Skip to content

Updating a file via LargeFileUploadTask does not work (NullPointerException) #1517

@kekolab

Description

@kekolab

This is a duplication of #microsoftgraph/msgraph-sdk-java#1804.
Initially, I opened the issue on that repo, but it is more correct opening it here

As stated in the object, updating (i.e. modifying) a file via the LargeFileUploadTask does not work and results in a NullPointerException.

Expected behavior

Not receiving a NullPointerException

Actual behavior

Receiving a NullPointerException

API version

implementation 'com.microsoft.graph:microsoft-graph:6.2.0' // Include the sdk as a dependency

Steps to reproduce the behavior

  1. Create a new file
DriveItemUploadableProperties uploadableProperties = new DriveItemUploadableProperties();
uploadableProperties.setName(name);
uploadableProperties.setFileSize(length);
uploadableProperties.getAdditionalData().put("@microsoft.graph.conflictBehavior", "fail");
CreateUploadSessionPostRequestBody uploadSessionPostRequestBody = new CreateUploadSessionPostRequestBody();
uploadSessionPostRequestBody.setItem(uploadableProperties);
UploadSession uploadSession = drive.items().byDriveItemId(parent.getId() + ":/" + name + ":").createUploadSession().post(uploadSessionPostRequestBody);
LargeFileUploadTask<DriveItem> largeFileUploadTask = new  LargeFileUploadTask<>(
client.getRequestAdapter(), uploadSession, content, length, DriveItem::createFromDiscriminatorValue);
UploadResult<DriveItem> uploadResult = largeFileUploadTask.upload();
  1. Update it
    Note the difference in the upload session creation: above (creation) we use the parentId and the file name byDriveItemId(parent.getId() + ":/" + name + ":") while, below (update), we use the file id byDriveItemId(id)
DriveItemUploadableProperties uploadableProperties = new DriveItemUploadableProperties();
uploadableProperties.setName(name);
uploadableProperties.setFileSize(length);
uploadableProperties.getAdditionalData().put("@microsoft.graph.conflictBehavior", "replace");
CreateUploadSessionPostRequestBody uploadSessionPostRequestBody = new CreateUploadSessionPostRequestBody();
uploadSessionPostRequestBody.setItem(uploadableProperties);
UploadSession uploadSession = drive.items().byDriveItemId(id).createUploadSession().post(uploadSessionPostRequestBody);
LargeFileUploadTask<DriveItem> largeFileUploadTask = new  LargeFileUploadTask<>(
client.getRequestAdapter(), uploadSession, content, length, DriveItem::createFromDiscriminatorValue);
UploadResult<DriveItem> uploadResult = largeFileUploadTask.upload();
  1. You have a NullPointerException
java.lang.NullPointerException: Cannot invoke "java.util.Collection.toArray()" because "c" is null
 at java.base/java.util.ArrayList.([ArrayList.java:181](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at com.microsoft.graph.core.models.UploadSession.getNextExpectedRanges([UploadSession.java:61](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at com.microsoft.graph.core.requests.upload.UploadResponseHandler.handleResponse([UploadResponseHandler.java:79](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at com.microsoft.graph.core.requests.upload.UploadSliceRequestBuilder.put([UploadSliceRequestBuilder.java:69](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at com.microsoft.graph.core.tasks.LargeFileUploadTask.uploadSlice([LargeFileUploadTask.java:207](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at com.microsoft.graph.core.tasks.LargeFileUploadTask.upload([LargeFileUploadTask.java:131](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
 at com.microsoft.graph.core.tasks.LargeFileUploadTask.upload([LargeFileUploadTask.java:111](vscode-file://vscode-app/c:/Users/03200692/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html))
[...]

Important

The file on the onedrive is correctly updated!!!

Bug hunting

When you create a file, the server returns a 201 Created with the following response: :

{
   "createdBy":{
      "application":{
         "id":"4c3cb947"
      },
      "user":{
         "id":"83a259e7e5f58e69"
      }
   },
   "createdDateTime":"2024-02-21T10:53:40.397Z",
   "cTag":"aYzo4M0EyNTlFN0U1RjU4RTY5ITE3NjM4OS4yNTc",
   "eTag":"aODNBMjU5RTdFNUY1OEU2OSExNzYzODkuMA",
   "id":"83A259E7E5F58E69!176389",
   "lastModifiedBy":{
      "application":{
         "id":"4c3cb947"
      },
      "user":{
         "id":"83a259e7e5f58e69"
      }
   },
   "lastModifiedDateTime":"2024-02-21T10:53:40.397Z",
   "name":"f1",
   "parentReference":{
      "driveId":"83a259e7e5f58e69",
      "driveType":"personal",
      "id":"83A259E7E5F58E69!176388",
      "name":"testFolder1708512810754",
      "path":"/drive/root:/testFolder1708512810754"
   },
   "size":4096,
   "webUrl":"https://1drv.ms/u/s!AGmO9eXnWaKDiuIF",
   "items":[
      
   ],
   "file":{
      "hashes":{
         "quickXorHash":"5gRtDBd9iwj8tz2tb1ViJeM3gX8=",
         "sha1Hash":"3CCFE9D871858B15B3F00FCB0AF47D171D9D9E2F",
         "sha256Hash":"55A65AE013961B1FC704C0371DC4A6936A6251685B09B014AB48BDAC27258AC7"
      },
      "mimeType":"application/octet-stream"
   },
   "fileSystemInfo":{
      "createdDateTime":"2024-02-21T10:53:40.396Z",
      "lastModifiedDateTime":"2024-02-21T10:53:40.396Z"
   },
   "reactions":{
      "commentCount":0
   },
   "tags":[
      
   ],
   "lenses":[
      
   ]
}

while, when you update a file the server returns a 200 OK with the following response:

{
   "createdBy":{
      "application":{
         "id":"4c3cb947"
      },
      "user":{
         "id":"83a259e7e5f58e69"
      }
   },
   "createdDateTime":"2024-02-21T10:53:40.397Z",
   "cTag":"aYzo4M0EyNTlFN0U1RjU4RTY5ITE3NjM4OS4yNTg",
   "eTag":"aODNBMjU5RTdFNUY1OEU2OSExNzYzODkuMw",
   "id":"83A259E7E5F58E69!176389",
   "lastModifiedBy":{
      "application":{
         "id":"4c3cb947"
      },
      "user":{
         "id":"83a259e7e5f58e69"
      }
   },
   "lastModifiedDateTime":"2024-02-21T10:54:04.21Z",
   "name":"f1",
   "parentReference":{
      "driveId":"83a259e7e5f58e69",
      "driveType":"personal",
      "id":"83A259E7E5F58E69!176388",
      "name":"testFolder1708512810754",
      "path":"/drive/root:/testFolder1708512810754"
   },
   "size":4096,
   "webUrl":"https://1drv.ms/u/s!AGmO9eXnWaKDiuIF",
   "items":[
      
   ],
   "file":{
      "hashes":{
         "quickXorHash":"T43AsKcwz5bu2iAV5IcCghvWqRY=",
         "sha1Hash":"2C8C824E1300C1FE6F3A8C919F9ECDD2CD47CF8D",
         "sha256Hash":"7FF463935CD4132EBA2958AB568514A249EFA2A0A7E07CC2916223603162FD65"
      },
      "mimeType":"application/octet-stream"
   },
   "fileSystemInfo":{
      "createdDateTime":"2024-02-21T10:53:40.396Z",
      "lastModifiedDateTime":"2024-02-21T10:54:04.193Z"
   },
   "reactions":{
      "commentCount":0
   },
   "tags":[
      
   ],
   "lenses":[
      
   ]
}

The response is the same (and, actually, it's as documented in the REST reference, but the problem is in the HTTP response code. The response, indeed, is parsed by com.microsoft.graph.core.requests.upload.UploadResponseHandler#handleResponse:

        Objects.requireNonNull(response);
        Objects.requireNonNull(factory);
        try (final ResponseBody body = response.body()) {
            if (Objects.isNull(body)) {
                throw new ApiException(ErrorConstants.Messages.NO_RESPONSE_FOR_UPLOAD);
            }
            try(final InputStream in = body.byteStream()){
                final String contentType = body.contentType().toString().split(";")[0]; //contentType.toString() returns in format <mediaType>;<charset>, we only want the mediaType.
                if(!response.isSuccessful()) {
                    throw new ApiExceptionBuilder()
                            .withMessage(ErrorConstants.Codes.GENERAL_EXCEPTION)
                            .withResponseStatusCode(response.code())
                            .withResponseHeaders(HeadersCompatibility.getResponseHeaders(response.headers()))
                            .build();
                }
                UploadResult<T> uploadResult = new UploadResult<>();
                if (response.code() == HttpURLConnection.HTTP_CREATED) {
                    if (body.contentLength() > 0) {
                        final ParseNode uploadTypeParseNode = parseNodeFactory.getParseNode(contentType, in);
                        uploadResult.itemResponse = uploadTypeParseNode.getObjectValue(factory);
                    }
                    final String location = response.headers().get("location");
                    if(!Objects.isNull(location) && !location.isEmpty()) {
                        uploadResult.location = new URI(location);
                    }
                } else {
                    final ParseNode parseNode = parseNodeFactory.getParseNode(contentType, in);
                    final UploadSession uploadSession = parseNode.getObjectValue(UploadSession::createFromDiscriminatorValue);
                    if (!uploadSession.getNextExpectedRanges().isEmpty()) {
                        uploadResult.uploadSession = uploadSession;
                    } else {
                        uploadResult.itemResponse = parseNode.getObjectValue(factory);
                    }
                }
                return uploadResult;
            }
        }
        catch(IOException | URISyntaxException ex) {
            throw new RuntimeException(ex);
        }

At the 18th line there is the block that manages the creation response.code() == HttpURLConnection.HTTP_CREATED; if you update the file instead of creating it, you receive a 201 and go in the else block which fails at the line if (!uploadSession.getNextExpectedRanges().isEmpty()) because the received JSON (see above) does not have any property called nextExpectedRanges thus uploadSession.getNextExpectedRanges() returns null and invoking isEmpty() causes the NPE.

Possible solution

Replace line 18 of the method:

if (response.code() == HttpURLConnection.HTTP_CREATED) {

with:

int responseCode = response.code();
if (responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpURLConnection.HTTP_OK) {

I haven't tried it (not quite at ease with github and pull requests, but that should work)

Metadata

Metadata

Assignees

Labels

Standard GitHub labelIssue caused by core project dependency modules or librarybugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions