microsoftgraph / microsoftgraph/msgraph-sdk-java

JsonSyntaxException in BatchResponseContent when batch response contains non-JSON body

Open
#2,472 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status:waiting-for-triage type:bug
Dominant language
Java
Stars
444
Forks
154
Avg merge
18h 28m
Merged PRs (30d)
4

Description

Describe the bug

When making a batch request using the Microsoft Graph Java SDK, the SDK fails to parse the batch response if one of the individual responses contains a plain text body instead of valid JSON.

com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 149 path $.responses[1].body
See https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json
        at com.google.gson.internal.Streams.parse(Streams.java:58) ~[gson-2.11.0.jar!/:na]
        at com.google.gson.JsonParser.parseReader(JsonParser.java:146) ~[gson-2.11.0.jar!/:na]
        at com.google.gson.JsonParser.parseReader(JsonParser.java:110) ~[gson-2.11.0.jar!/:na]
        at com.microsoft.graph.core.content.BatchResponseContent.getBatchResponseContent(BatchResponseContent.java:168) ~[microsoft-graph-core-3.6.1.jar!/:na]
        ...
Caused by: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 149 path $.responses[1].body

The raw batch response returned by the API looks like this (simplified for clarity):

{
  "responses": [
    {
      "id": "a063d4a4-XXXX-4433-8704-6b1252f7e864",
      "status": 200,
      "headers": {
        "Cache-Control": "private",
        "Content-Type": "application/json; odata.metadata=minimal; odata.streaming=true; IEEE754Compatible=false; charset=utf-8"
      },
      "body": {
        "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('user%40domain.com')/events(id)",
        "value": [
          {
            "@odata.etag": "W/\"a+n3FpwGr06xxxxxxxxxjmCBjQ==\"",
            "id": "AAMkAGVlYjJjNGxxxxxxxABGAAAAAAAkmk-HOxjKRZmNlLfGqYjaBwBr6fcWnAavTpXG0aD="
          }
        ]
      }
    },
    {
      "id": "5f956687-XXXX-4aa4-a726-dc6b9810db1e",
      "status": 503,
      "headers": {
        "Cache-Control": "private"
      },
      "body": Authentication Concurrency Limit Reached
    }
  ]
}

As you can see, the second response has a body field that is not valid JSON (Authentication Concurrency Limit Reached), which causes the SDK to throw a JsonSyntaxException when parsing.

There is also another variant that falls into the same error:

{
    "responses": [
        {
            "id": "6ea85c49-XXXX-4116-9844-2b0cbbf52ea3",
            "status": 503,
            "headers": {
                "Content-Type": "text/html; charset=us-ascii"
            },
            "body":<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Service Unavailable</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Application Request Queue Full</h2>
<hr><p>HTTP Error 503. The application request queue is full.</p>
</BODY></HTML>
        }
    ]
}
Expected behavior

The SDK should be able to handle non-JSON response bodies gracefully in batch responses. For example:

  • Return the raw string body when the response is not valid JSON.
  • Or provide a way to detect and handle such cases without throwing a parsing exception.
How to reproduce
  1. Use the GraphServiceClient to send multiple requests (20 request per batch) in parallel (+4 in parallel) using GraphServiceClient().getBatchRequestBuilder().post()
  2. When the API is under load, one of the requests may return a 503 Authentication Concurrency Limit Reached error.
  3. The batch response will then contain a body field with plain text instead of valid JSON.
  4. Attempting to parse the batch response with BatchResponseContent.getResponseById(...) will throw a JsonSyntaxException.

To see the raw API response add the following interceptor to the OkHttpClient

@Slf4j
public class RawResponseLoggingInterceptor implements Interceptor {

    @NotNull
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);

        ResponseBody responseBody = response.body();
        if (responseBody != null) {
            try {
                MediaType contentType = responseBody.contentType();
                String rawJson = responseBody.string();
                log.info("[API] Raw JSON Response for URL [{}], Status [{}]: {}", request.url(), response.code(), rawJson);
                ResponseBody newResponseBody = ResponseBody.create(rawJson, contentType);
                return response.newBuilder().body(newResponseBody).build();
            } catch (Exception e) {
                log.error("[API] Error reading raw response body for URL [{}]", request.url(), e);
            }
        }
        return response;
    }
}
RawResponseLoggingInterceptor loggingInterceptor = new RawResponseLoggingInterceptor();

OkHttpClient customOkHttpClient = new OkHttpClient.Builder()
      .addInterceptor(loggingInterceptor)
      .build();

GraphServiceClient client = new GraphServiceClient(authProvider, customOkHttpClient);
SDK Version

6.51.0

Latest version known to work for scenario above?

No response

Known Workarounds

No response

Debug output
Click to expand log ```

com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 149 path $.responses[0].body
See https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json
at com.google.gson.internal.Streams.parse(Streams.java:58) ~[gson-2.11.0.jar!/:na]
at com.google.gson.JsonParser.parseReader(JsonParser.java:146) ~[gson-2.11.0.jar!/:na]
at com.google.gson.JsonParser.parseReader(JsonParser.java:110) ~[gson-2.11.0.jar!/:na]
at com.microsoft.graph.core.content.BatchResponseContent.getBatchResponseContent(BatchResponseContent.java:168) ~[microsoft-graph-core-3.6.1.jar!/:na]
at com.microsoft.graph.core.content.BatchResponseContent.getResponseById(BatchResponseContent.java:94) ~[microsoft-graph-core-3.6.1.jar!/:na]
at java.base/java.util.HashMap.forEach(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Unknown Source) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.6.jar!/:6.2.6]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.2.6.jar!/:6.2.6]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.2.6.jar!/:6.2.6]
at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:114) ~[spring-aop-6.2.6.jar!/:6.2.6]
at java.base/java.util.concurrent.FutureTask.run(Unknown Source) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) ~[na:na]
at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
Caused by: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 149 path $.responses[0].body
See https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json
at com.google.gson.stream.JsonReader.syntaxError(JsonReader.java:1754) ~[gson-2.11.0.jar!/:na]
at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:574) ~[gson-2.11.0.jar!/:na]
at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:498) ~[gson-2.11.0.jar!/:na]
at com.google.gson.internal.bind.TypeAdapters$28.read(TypeAdapters.java:875) ~[gson-2.11.0.jar!/:na]
at com.google.gson.internal.bind.TypeAdapters$28.read(TypeAdapters.java:820) ~[gson-2.11.0.jar!/:na]
at com.google.gson.internal.Streams.parse(Streams.java:46) ~[gson-2.11.0.jar!/:na]
... 18 common frames omitted

</details>


### Configuration

- OS: Windows 11 Pro 24H2 
- Architecture: x64
- JDK: OpenJDK Runtime Environment Temurin-21.0.3+9 (build 21.0.3+9-LTS)

### Other information

_No response_

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in com.microsoft.graph.core.content.BatchResponseContent, especially getBatchResponseContent at the stack-trace location and getResponseById. Reproduce parsing with the plain-text and HTML batch bodies shown in the issue, then verify that non-JSON response bodies no longer cause JsonSyntaxException and remain available for handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.