square/retrofit · error · JsonIOException

JSON document was not fully consumed.

Error message

JSON document was not fully consumed.

What it means

JsonIOException thrown by GsonResponseBodyConverter when, after the TypeAdapter finishes reading a value, the JsonReader is not positioned at END_DOCUMENT (line 41). This means the response body contained trailing content after the first complete JSON value — Gson requires the body to be exactly one JSON value. The converter enforces this strictly to surface malformed responses and buggy custom adapters rather than silently dropping data.

Source

Thrown at retrofit-converters/gson/src/main/java/retrofit2/converter/gson/GsonResponseBodyConverter.java:42

import okhttp3.ResponseBody;
import retrofit2.Converter;

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;

  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }

  @Override
  public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      T result = adapter.read(jsonReader);
      if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
        throw new JsonIOException("JSON document was not fully consumed.");
      }
      return result;
    } finally {
      value.close();
    }
  }
}

View on GitHub (pinned to d0b112dad0)

Solutions

  1. Log the raw body with an OkHttp HttpLoggingInterceptor at BODY level (non-production) to see the trailing content.
  2. Fix the server to return exactly one JSON value with no trailing data.
  3. If trailing content is expected/unavoidable, register a custom Converter.Factory that reads only the first value, or a TypeAdapter that consumes the remainder.
  4. If a custom TypeAdapter is in use, ensure it reads every token (do not return while peek() is not END_OBJECT/END_ARRAY).

Example fix

// before (server returns: {"id":1}GARBAGE)
// client throws JsonIOException: JSON document was not fully consumed.

// after — custom lenient converter reads only the first JSON value
final class LenientGsonConverterFactory extends Converter.Factory {
  private final Gson gson;
  LenientGsonConverterFactory(Gson gson) { this.gson = gson; }
  @Override public Converter<ResponseBody, ?> responseBodyConverter(
      Type type, Annotation[] anns, Retrofit rf) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return body -> {
      try (JsonReader reader = gson.newJsonReader(body.charStream())) {
        reader.setLenient(true);
        @SuppressWarnings("unchecked") Object result = ((TypeAdapter<Object>) adapter).read(reader);
        return result; // ignore any trailing content
      } finally { body.close(); }
    };
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Inspect the raw body once (non-production) to confirm it is a single JSON value.
// Note: a ResponseBody can be read only once, so this is for diagnostics, not a
// per-request guard. Use an OkHttp interceptor in debug builds:
HttpLoggingInterceptor logger = new HttpLoggingInterceptor(msg -> Log.d("HTTP", msg));
logger.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(logger).build();

Try / catch

// JsonIOException is an IOException; handle it at the call site alongside network errors.
service.getUser(id).enqueue(new Callback<User>() {
  @Override public void onResponse(Call<User> c, Response<User> r) { /* ... */ }
  @Override public void onFailure(Call<User> c, Throwable t) {
    if (t instanceof com.google.gson.JsonIOException) {
      // body had trailing content / multiple JSON values; log and report a
      // contract violation rather than retrying blindly
      reportMalformedResponse(t);
    } else {
      // ordinary IOException (network, timeout)
    }
  }
});

Prevention

When it happens

Trigger: Server returns concatenated JSON values (`{"a":1}{"b":2}`), trailing garbage after the JSON (`{"a":1}<<<`), an HTML error page with embedded JSON, a BOM or whitespace-bounded extra tokens, or a custom TypeAdapter that returns early without consuming the full token stream.

Common situations: Backend bug appending a second object or a debug log line after the JSON; a transparent proxy/CDN injecting content; mismatched Content-Type serving an HTML error page; a custom Gson TypeAdapter that does not consume nested tokens; chunked/transfer-encoding artifacts; API version change adding a wrapper the client does not expect.

Related errors


AI-assisted analysis of square/retrofit@d0b112dad0 (2026-08-04). Data as JSON: /data/errors/4a8ad521d5ab3c44.json. Report an issue: GitHub.