google/gson · error · IllegalStateException
Unexpected when reading a JsonElement.
Error message
Unexpected ${peeked} when reading a JsonElement. What it means
JsonTreeReader.nextJsonElement (used by JsonElementTypeAdapter when reading from a JsonElement tree) requires the current token to be a value or the start of an array/object. If peek() returns NAME, END_ARRAY, END_OBJECT, or END_DOCUMENT, it throws IllegalStateException. This signals the read was attempted at a position where no complete element can be produced.
Solutions
- Only read a JsonElement when the reader is positioned at a value or container start (check peek() first).
- Create a fresh JsonTreeReader from the JsonElement for each parse rather than reusing.
- Avoid mixing manual token consumption with JsonElement extraction on the same reader.
- If you need the whole tree, use the JsonElement directly instead of re-reading through a JsonTreeReader.
Example fix
// before: reader already consumed past the value val reader = JsonTreeReader(tree) reader.beginObject(); reader.nextName() JsonElementTypeAdapter.ADAPTER.read(reader) // Unexpected NAME when reading a JsonElement // after: read element before advancing into object structure val reader = JsonTreeReader(tree) val el = JsonElementTypeAdapter.ADAPTER.read(reader)
Defensive patterns
Strategy: validation
Validate before calling
// Position the reader at a value/container before extracting a JsonElement
JsonToken t = reader.peek();
if (t == NAME || t == END_ARRAY || t == END_OBJECT || t == END_DOCUMENT) {
throw new IllegalStateException("Cannot read JsonElement at " + t);
}
JsonElement e = JsonElementTypeAdapter.ADAPTER.read(reader); Try / catch
try {
reader.nextJsonElement();
} catch (IllegalStateException e) {
if (e.getMessage().startsWith("Unexpected ") && e.getMessage().contains("when reading a JsonElement")) {
// reader mis-positioned; reset to a fresh JsonTreeReader
throw new IllegalStateException("Re-read from a new reader", e);
}
throw e;
} Prevention
- Create a fresh JsonTreeReader per parse rather than reusing.
- Use the JsonElement directly instead of re-reading through a JsonTreeReader.
- Check peek() before extracting a JsonElement.
- Do not mix manual token consumption with JsonElement extraction.
When it happens
Trigger: Calling gson.fromJson(jsonElement, JsonElement.class) or otherwise reading a JsonElement out of a JsonTreeReader positioned at a name token or an end-of-container token. Reproducible by re-reading a tree at the wrong cursor position.
Common situations: Reusing a JsonTreeReader past its valid cursor; calling nextJsonElement after hasNext() returned false; integrating JsonElement trees with custom adapters that mis-position the reader.
Related errors
- Unexpected token
- Custom JsonElement subclass
- Couldn't write
- Expected but was at path
- Expected NUMBER but was
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/d46fa01912c1d018.
Report an issue: GitHub.
Appendix: source
Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java:317
try {
result = primitive.getAsInt();
} catch (NumberFormatException e) {
throw numberFormatException("Expected an int but was " + primitive.getAsString(), e);
}
popStack();
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
return result;
}
JsonElement nextJsonElement() throws IOException {
JsonToken peeked = peek();
if (peeked == JsonToken.NAME
|| peeked == JsonToken.END_ARRAY
|| peeked == JsonToken.END_OBJECT
|| peeked == JsonToken.END_DOCUMENT) {
throw new IllegalStateException("Unexpected " + peeked + " when reading a JsonElement.");
}
JsonElement element = (JsonElement) peekStack();
skipValue();
return element;
}
@Override
public void close() throws IOException {
stack = new Object[] {SENTINEL_CLOSED};
stackSize = 1;
}
@Override
public void skipValue() throws IOException {
JsonToken peeked = peek();
switch (peeked) {
case NAME:
@SuppressWarnings("unused")View on GitHub (pinned to 310ac341f2)