google/gson · error · IOException
Incomplete document
Error message
Incomplete document
What it means
Thrown by JsonWriter.close() when the document is not balanced: stackSize > 1 (an array or object is still open) or stackSize == 1 but the lone frame is not NONEMPTY_DOCUMENT (i.e. the writer produced zero top-level values, an empty document). Gson enforces that a closed writer yields exactly one complete, well-formed JSON value, so partial structure is treated as an I/O failure. This is an IOException, not a runtime exception, because it surfaces during resource cleanup.
Source
Thrown at gson/src/main/java/com/google/gson/stream/JsonWriter.java:724
public void flush() throws IOException {
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
}
out.flush();
}
/**
* Flushes and closes this writer and the underlying {@link Writer}.
*
* @throws IOException if the JSON document is incomplete.
*/
@Override
public void close() throws IOException {
out.close();
int size = stackSize;
if (size > 1 || (size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT)) {
throw new IOException("Incomplete document");
}
stackSize = 0;
}
/** Returns whether the {@code toString()} of {@code c} will always return a valid JSON number. */
private static boolean alwaysCreatesValidJsonNumber(Class<? extends Number> c) {
// Does not include Float or Double because their value can be NaN or Infinity
// Does not include LazilyParsedNumber because it could contain a malformed string
return c == Integer.class
|| c == Long.class
|| c == Byte.class
|| c == Short.class
|| c == BigDecimal.class
|| c == BigInteger.class
|| c == AtomicInteger.class
|| c == AtomicLong.class;
}
View on GitHub (pinned to 8b8628c656)
Solutions
- Always pair beginObject/endObject and beginArray/endArray, ideally via try-with-resources which calls the matching end method on close for nested scopes (JsonWriter implements AutoCloseable for the top-level close only, so track nesting explicitly).
- In error/finally paths, balance the stack before close() or swallow the expected IOException from close() after an upstream failure.
- Ensure at least one top-level value is written before close() if the writer is expected to emit valid JSON.
- Separate the failure case: if serialization already failed, do not propagate the secondary 'Incomplete document' from close(); log/suppress it.
Example fix
// before
writer.beginArray();
for (Item i : items) writeItem(writer, i);
writer.close(); // throws if items loop threw early
// after
writer.beginArray();
try {
for (Item i : items) writeItem(writer, i);
} finally {
// balance the structure regardless of failure
try { writer.endArray(); } catch (IOException ignored) {}
}
writer.close(); Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure balanced structure before close(): maintain a depth counter yourself
private int depth = 0;
void begin() throws IOException { writer.beginArray(); depth++; }
void end() throws IOException { writer.endArray(); depth--; }
void safeClose() throws IOException {
while (depth > 0) { try { writer.endArray(); } catch (IOException ignored) {} depth--; }
writer.close();
} Type guard
// Structural invariant: a well-formed document has exactly one top-level value
// and no open containers. This guard wraps close() to verify depth.
public boolean isDocumentComplete(int openContainers, boolean wroteTopLevel) {
return openContainers == 0 && wroteTopLevel;
} Try / catch
// On an already-failing write path, suppress the secondary close() error
try {
writer.close();
} catch (IOException e) {
if (!"Incomplete document".equals(e.getMessage()) && primaryFailure == null) throw e;
// else: primary failure already being propagated; log secondary
} Prevention
- Always pair beginObject/endObject and beginArray/endArray; track depth explicitly.
- On error paths, balance open containers in finally before close(), or accept the secondary IOException.
- Ensure at least one top-level value is written before close().
- Distinguish a genuine serialization failure from the cascading 'Incomplete document' so you fix the root cause.
When it happens
Trigger: Calling writer.close() after beginObject()/beginArray() without matching endObject()/endArray(); closing a brand-new writer that never wrote a top-level value; an exception aborting serialization mid-structure so the finally block's close() runs with open containers; streaming a partial response then closing early.
Common situations: Error-path finally blocks that call close() on a partially-built document; streaming APIs where the consumer disconnects and the writer is closed with an open array; serializers that conditionally skip endObject() on an early return; testing helpers that close without completing output.
Related errors
- JSON forbids NaN and infinities: {value}
- Incomplete document
- JsonReader is closed
- JsonWriter is closed.
- Numeric values must be finite, but was " + string
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/3823de80e6ba1c37.json.
Report an issue: GitHub.