{"id":"6e350cf401c10374","repo":"google/gson","slug":"expected-one-json-element-but-was-stack","errorCode":null,"errorMessage":"Expected one JSON element but was {stack}","messagePattern":"Expected one JSON element but was (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java","lineNumber":71,"sourceCode":"  private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive(\"closed\");\n\n  /** The JsonElements and JsonArrays under modification, outermost to innermost. */\n  private final List<JsonElement> stack = new ArrayList<>();\n\n  /** The name for the next JSON object value. If non-null, the top of the stack is a JsonObject. */\n  private String pendingName;\n\n  /** the JSON element constructed by this writer. */\n  private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?;\n\n  public JsonTreeWriter() {\n    super(UNWRITABLE_WRITER);\n  }\n\n  /** Returns the top level object produced by this writer. */\n  public JsonElement get() {\n    if (!stack.isEmpty()) {\n      throw new IllegalStateException(\"Expected one JSON element but was \" + stack);\n    }\n    return product;\n  }\n\n  private JsonElement peek() {\n    return stack.get(stack.size() - 1);\n  }\n\n  private void put(JsonElement value) {\n    if (pendingName != null) {\n      if (!value.isJsonNull() || getSerializeNulls()) {\n        JsonObject object = (JsonObject) peek();\n        object.add(pendingName, value);\n      }\n      pendingName = null;\n    } else if (stack.isEmpty()) {\n      product = value;\n    } else {","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java#L53-L89","documentation":"Thrown by JsonTreeWriter.get() when the internal stack is non-empty, meaning there are still-open JSON arrays or objects that were begun with beginArray()/beginObject() but never closed. The writer cannot return a single root JsonElement because the document is structurally incomplete. The exception message lists the leftover stack to aid debugging.","triggerScenarios":"Calling JsonTreeWriter.get() (or equivalently letting a TypeAdapter that writes to a JsonTreeWriter finish) without first calling endObject()/endArray() for every beginObject()/beginArray(). Imbalance of begin/end calls is the only trigger.","commonSituations":"Custom serializer (TypeAdapter.write or JsonSerializer) that calls beginObject() but returns early on an exception path without closing; forgetting to match every beginArray() with endArray(); adapter that conditionally opens a structure then skips the close on a branch; tests that build a tree manually and forget to close.","solutions":["Match every beginObject()/beginArray() with a corresponding endObject()/endArray() in a try/finally so exceptions can't leave the stack open.","Before calling get(), assert the writer has no open containers; if unsure, track depth yourself.","Restructure the serializer to write its root element with a single value(...) call if it only emits one scalar, avoiding begin/end entirely.","Use try-with-resources style discipline: pair begin/end in the same lexical scope."],"exampleFix":"// before: beginObject() with no matching endObject()\nJsonTreeWriter w = new JsonTreeWriter();\nw.beginObject().name(\"a\").value(1);\nw.get(); // throws: stack not empty\n\n// after: always close what you open\nJsonTreeWriter w = new JsonTreeWriter();\nw.beginObject();\ntry {\n  w.name(\"a\").value(1);\n} finally {\n  w.endObject();\n}\nw.get();","handlingStrategy":"validation","validationCode":"// Ensure all containers are closed before calling get()\n// Track depth externally and assert zero before get()\nif (openDepth != 0) throw new IllegalStateException(openDepth + \" unclosed containers\");\nJsonElement root = treeWriter.get();","typeGuard":null,"tryCatchPattern":"try {\n  return treeWriter.get();\n} catch (IllegalStateException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"Expected one JSON element\")) {\n    // attempt to close open containers, or fail with context\n    throw new IllegalStateException(\"Document incomplete: unclosed containers remain\", e);\n  }\n  throw e;\n}","preventionTips":["Pair every beginObject()/beginArray() with a matching end in a finally block.","Never call get() before closing the root container.","Add a depth assertion in development builds to catch imbalance early."],"tags":["json","streaming","writer-state","gson","resource-leak"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}