{"record":{"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/310ac341f2f92a454b229bf21f70d2d18b2b6db7/gson/src/main/java/com/google/gson/stream/JsonWriter.java#L706-L742","documentation":"Thrown by JsonWriter.close() (an IOException, not IllegalStateException) when the writer's internal scope stack is not in a terminal state: either open containers remain (stackSize > 1) or a single frame exists but it is not NONEMPTY_DOCUMENT (meaning no top-level value was ever written, or an array/object was opened and the document ended mid-stream). The check lives at JsonWriter.java:723-725. It enforces that close() is only valid after exactly one complete top-level JSON value, per RFC 8259.","triggerScenarios":"Calling writer.close() without having written any value at all (fresh writer, stack still EMPTY_DOCUMENT); calling close() after beginArray()/beginObject() without the matching endArray()/endObject(); calling close() after writing a single literal but the stream still has an unclosed container; forgetting to wrap a top-level value so the stack frame is still EMPTY_DOCUMENT at close time.","commonSituations":"Early-return or exception paths in serialization code that skip the endObject()/endArray() calls but still close() the writer (try-with-resources closing on a partially-built document); recursive serializers that bail out partway; refactoring that adds beginObject() without updating the finally block; writing into an empty StringWriter for a DTO that serializes to nothing.","solutions":["Ensure every beginArray()/beginObject() has a matching endArray()/endObject() before close(); structure the writing block so the closes run in a finally that only executes after the opens.","If using try-with-resources, make sure the document body is fully constructed inside the try block and close happens only after the top-level value is complete.","Verify a top-level value is actually written: a brand-new writer closed immediately throws because the single frame is EMPTY_DOCUMENT, not NONEMPTY_DOCUMENT.","Track depth with a counter or use a helper that auto-balances scopes if you cannot guarantee symmetric calls on error paths."],"exampleFix":"// before\nJsonWriter w = new JsonWriter(new StringWriter());\nw.beginObject();\nw.name(\"a\").value(1);\nw.close(); // throws: object never closed\n\n// after\nJsonWriter w = new JsonWriter(new StringWriter());\nw.beginObject();\nw.name(\"a\").value(1);\nw.endObject();\nw.close();","handlingStrategy":"validation","validationCode":"// Cannot query stack depth directly; mirror scope depth yourself\nclass JsonWriterGuard {\n  private final JsonWriter w;\n  private int depth = 0; // open containers\n  private boolean wroteTopLevel = false;\n  JsonWriterGuard(JsonWriter w) { this.w = w; }\n  void openArray() throws IOException { w.beginArray(); depth++; }\n  void openObject() throws IOException { w.beginObject(); depth++; }\n  void closeArray() throws IOException { w.endArray(); depth--; }\n  void closeObject() throws IOException { w.endObject(); depth--; }\n  void markValue() throws IOException { /* call after each value */ if (depth == 0) wroteTopLevel = true; }\n  /** Call before close(): returns false if close() would throw 'Incomplete document'. */\n  boolean isSafeToClose() { return depth == 0 && wroteTopLevel; }\n}","typeGuard":"null","tryCatchPattern":"// close() throws IOException; ensure scopes balanced in finally\ntry (JsonWriter w = new JsonWriter(out)) {\n  w.beginObject();\n  try {\n    w.name(\"k\").value(1);\n  } finally {\n    w.endObject(); // guarantee container close before outer close\n  }\n} catch (IOException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"Incomplete document\")) {\n    // log and surface as a serialization bug, not a clean shutdown\n  }\n  throw e;\n}","preventionTips":["Match every beginArray/beginObject with endArray/endObject inside the same try block, ideally in a finally.","Never let a value/begin call leak across a close() boundary; close only after the top-level value is complete.","Write a thin wrapper that tracks open-scope depth and refuses close() unless depth==0 and at least one value was written.","Unit-test serializers with an empty input and a nested input to catch unbalanced scope paths."],"tags":["json","gson","streaming","resource-lifecycle","java"],"backgroundTag":null,"analyzedSha":"310ac341f2f92a454b229bf21f70d2d18b2b6db7","analyzedAt":"2026-08-10T02:58:47.455Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}