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

  1. 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).
  2. In error/finally paths, balance the stack before close() or swallow the expected IOException from close() after an upstream failure.
  3. Ensure at least one top-level value is written before close() if the writer is expected to emit valid JSON.
  4. 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

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


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