{"id":"3823de80e6ba1c37","repo":"google/gson","slug":"incomplete-document-3823de","errorCode":null,"errorMessage":"Incomplete document","messagePattern":"Incomplete document","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/stream/JsonWriter.java","lineNumber":724,"sourceCode":"  public void flush() throws IOException {\n    if (stackSize == 0) {\n      throw new IllegalStateException(\"JsonWriter is closed.\");\n    }\n    out.flush();\n  }\n\n  /**\n   * Flushes and closes this writer and the underlying {@link Writer}.\n   *\n   * @throws IOException if the JSON document is incomplete.\n   */\n  @Override\n  public void close() throws IOException {\n    out.close();\n\n    int size = stackSize;\n    if (size > 1 || (size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT)) {\n      throw new IOException(\"Incomplete document\");\n    }\n    stackSize = 0;\n  }\n\n  /** Returns whether the {@code toString()} of {@code c} will always return a valid JSON number. */\n  private static boolean alwaysCreatesValidJsonNumber(Class<? extends Number> c) {\n    // Does not include Float or Double because their value can be NaN or Infinity\n    // Does not include LazilyParsedNumber because it could contain a malformed string\n    return c == Integer.class\n        || c == Long.class\n        || c == Byte.class\n        || c == Short.class\n        || c == BigDecimal.class\n        || c == BigInteger.class\n        || c == AtomicInteger.class\n        || c == AtomicLong.class;\n  }\n","sourceCodeStart":706,"sourceCodeEnd":742,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/gson/src/main/java/com/google/gson/stream/JsonWriter.java#L706-L742","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nwriter.beginArray();\nfor (Item i : items) writeItem(writer, i);\nwriter.close(); // throws if items loop threw early\n\n// after\nwriter.beginArray();\ntry {\n  for (Item i : items) writeItem(writer, i);\n} finally {\n  // balance the structure regardless of failure\n  try { writer.endArray(); } catch (IOException ignored) {}\n}\nwriter.close();","handlingStrategy":"try-catch","validationCode":"// Ensure balanced structure before close(): maintain a depth counter yourself\nprivate int depth = 0;\nvoid begin() throws IOException { writer.beginArray(); depth++; }\nvoid end() throws IOException { writer.endArray(); depth--; }\n\nvoid safeClose() throws IOException {\n  while (depth > 0) { try { writer.endArray(); } catch (IOException ignored) {} depth--; }\n  writer.close();\n}","typeGuard":"// Structural invariant: a well-formed document has exactly one top-level value\n// and no open containers. This guard wraps close() to verify depth.\npublic boolean isDocumentComplete(int openContainers, boolean wroteTopLevel) {\n  return openContainers == 0 && wroteTopLevel;\n}","tryCatchPattern":"// On an already-failing write path, suppress the secondary close() error\ntry {\n  writer.close();\n} catch (IOException e) {\n  if (!\"Incomplete document\".equals(e.getMessage()) && primaryFailure == null) throw e;\n  // else: primary failure already being propagated; log secondary\n}","preventionTips":["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."],"tags":["json","gson","lifecycle","serialization","resource-management"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}