google/gson · error · IOException

Incomplete document

Error message

Incomplete document

What it means

Thrown by JsonTreeWriter.close() as an IOException when the stack is non-empty at close time, meaning there are unclosed JSON arrays or objects. close() requires a complete (balanced) document; leftover open containers make the document incomplete. After a successful close, a SENTINEL_CLOSED marker is pushed so further operations fail fast.

Source

Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java:250

  @CanIgnoreReturnValue
  @Override
  public JsonWriter nullValue() throws IOException {
    put(JsonNull.INSTANCE);
    return this;
  }

  @Override
  public JsonWriter jsonValue(String value) throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void flush() throws IOException {}

  @Override
  public void close() throws IOException {
    if (!stack.isEmpty()) {
      throw new IOException("Incomplete document");
    }
    stack.add(SENTINEL_CLOSED);
  }
}

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Ensure every beginObject()/beginArray() is matched by endObject()/endArray() before close(); use try/finally to close containers even on exceptions, separate from the writer close().
  2. Don't rely on close() to fix up open containers; it intentionally throws to surface the bug.
  3. Track nesting depth and assert it is zero before close() in development builds.
  4. Restructure serializers so the close of containers happens in the same scope as the open.

Example fix

// before: try-with-resources closes while object still open
try (JsonTreeWriter w = new JsonTreeWriter()) {
  w.beginObject().name("a").value(1);
  // forgot endObject(); w.close() throws IOException: Incomplete document
}

// after: close containers before writer.close()
JsonTreeWriter w = new JsonTreeWriter();
w.beginObject();
try {
  w.name("a").value(1);
} finally {
  w.endObject();
}
w.close();
Defensive patterns

Strategy: validation

Validate before calling

// Close all containers before closing the writer
w.endObject(); // (and any nested end calls)
// only then:
w.close();

Try / catch

try {
  w.close();
} catch (IOException e) {
  if ("Incomplete document".equals(e.getMessage())) {
    // unclosed containers: log and discard, or attempt to balance
    throw new IOException("Cannot close: unclosed JSON containers remain", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling close() on a JsonTreeWriter that has had beginObject()/beginArray() without matching end calls. Common when a TypeAdapter uses try-with-resources on the writer (which calls close()) but its write path threw before closing all containers.

Common situations: A serializer wrapped in try-with-resources where an inner exception skips endObject()/endArray(); manual use of JsonTreeWriter that forgets to balance; adapters that early-return without closing nested structures; integration code that always closes the writer defensively.

Related errors


AI-assisted analysis of google/gson@8b8628c656 (2026-08-04). Data as JSON: /data/errors/bd0fa4c2110ecb76.json. Report an issue: GitHub.