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
- 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().
- Don't rely on close() to fix up open containers; it intentionally throws to surface the bug.
- Track nesting depth and assert it is zero before close() in development builds.
- 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
- Use try/finally to close containers before writer.close(); don't rely on close() to clean up.
- Track nesting depth and assert zero before close() in dev builds.
- Keep container open and close in the same lexical scope.
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
- Expected one JSON element but was {stack}
- Did not expect a name
- Please begin an object before writing a name.
- Unexpected {peeked} when reading a JsonElement.
- Incomplete document
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/bd0fa4c2110ecb76.json.
Report an issue: GitHub.