google/gson · error · IllegalStateException
Expected one JSON element but was {stack}
Error message
Expected one JSON element but was {stack} What it means
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.
Source
Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java:71
private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed");
/** The JsonElements and JsonArrays under modification, outermost to innermost. */
private final List<JsonElement> stack = new ArrayList<>();
/** The name for the next JSON object value. If non-null, the top of the stack is a JsonObject. */
private String pendingName;
/** the JSON element constructed by this writer. */
private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?;
public JsonTreeWriter() {
super(UNWRITABLE_WRITER);
}
/** Returns the top level object produced by this writer. */
public JsonElement get() {
if (!stack.isEmpty()) {
throw new IllegalStateException("Expected one JSON element but was " + stack);
}
return product;
}
private JsonElement peek() {
return stack.get(stack.size() - 1);
}
private void put(JsonElement value) {
if (pendingName != null) {
if (!value.isJsonNull() || getSerializeNulls()) {
JsonObject object = (JsonObject) peek();
object.add(pendingName, value);
}
pendingName = null;
} else if (stack.isEmpty()) {
product = value;
} else {View on GitHub (pinned to 8b8628c656)
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.
Example fix
// before: beginObject() with no matching endObject()
JsonTreeWriter w = new JsonTreeWriter();
w.beginObject().name("a").value(1);
w.get(); // throws: stack not empty
// after: always close what you open
JsonTreeWriter w = new JsonTreeWriter();
w.beginObject();
try {
w.name("a").value(1);
} finally {
w.endObject();
}
w.get(); Defensive patterns
Strategy: validation
Validate before calling
// Ensure all containers are closed before calling get() // Track depth externally and assert zero before get() if (openDepth != 0) throw new IllegalStateException(openDepth + " unclosed containers"); JsonElement root = treeWriter.get();
Try / catch
try {
return treeWriter.get();
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Expected one JSON element")) {
// attempt to close open containers, or fail with context
throw new IllegalStateException("Document incomplete: unclosed containers remain", e);
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Did not expect a name
- Please begin an object before writing a name.
- Incomplete document
- Unexpected {peeked} when reading a JsonElement.
- String contains non-ASCII characters: {s}{location}
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/6e350cf401c10374.json.
Report an issue: GitHub.