google/gson · error · IllegalStateException
Unexpected {peeked} when reading a JsonElement.
Error message
Unexpected {peeked} when reading a JsonElement. What it means
Thrown by JsonTreeReader.nextJsonElement() (an internal bridge called when a TypeAdapter/TypeAdapterFactory requests the current element as a JsonElement tree) when the reader is parked on a structural token that is not a value. Only NAME, END_ARRAY, END_OBJECT, and END_DOCUMENT are rejected; BEGIN_ARRAY, BEGIN_OBJECT, and all scalars are allowed. It signals that the caller asked for a complete element while the reader is mid-traversal of a container or at end of input.
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 8b8628c656)
Solutions
- Call nextName() (or beginObject()/beginArray()) to advance the reader off the NAME token before requesting the element as JsonElement.
- Guard with a peek() check: only call the JsonElement-reading path when peek() is BEGIN_OBJECT, BEGIN_ARRAY, STRING, NUMBER, BOOLEAN, or NULL.
- Ensure the reader has not already been exhausted (stackSize==0 -> END_DOCUMENT) before calling read; check hasNext() in your loop.
- If you wrote a custom adapter, restructure so you consume NAME tokens with nextName() and then read the value, rather than grabbing the whole element mid-object.
Example fix
// before: reader is on NAME, calling element read throws
if (reader.peek() == JsonToken.NAME) {
reader.nextName(); // consume the name first
}
JsonElement el = gson.fromJson(reader, JsonElement.class);
// after: only read element when positioned on a value token
JsonToken t = reader.peek();
if (t == JsonToken.NAME || t == JsonToken.END_ARRAY
|| t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT) {
throw new IllegalStateException("not at a value: " + t);
}
JsonElement el = gson.fromJson(reader, JsonElement.class); Defensive patterns
Strategy: validation
Validate before calling
// Validate reader position before requesting a JsonElement
JsonToken t = reader.peek();
if (t == JsonToken.NAME || t == JsonToken.END_ARRAY
|| t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT) {
throw new IllegalStateException("Reader not at a value token: " + t);
}
JsonElement el = gson.fromJson(reader, JsonElement.class); Type guard
// Guard: only true when safe to read a value element
static boolean atValueToken(JsonReader r) throws IOException {
JsonToken t = r.peek();
return t == JsonToken.BEGIN_OBJECT || t == JsonToken.BEGIN_ARRAY
|| t == JsonToken.STRING || t == JsonToken.NUMBER
|| t == JsonToken.BOOLEAN || t == JsonToken.NULL;
} Try / catch
try {
JsonElement el = gson.fromJson(reader, JsonElement.class);
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Unexpected ")) {
// reader was mid-container or at EOF; advance and recover
reader.skipValue();
} else throw e;
} Prevention
- Always pair nextName() with reading the value before doing element-level reads.
- Use peek() to confirm a value/container token before calling JsonElement-reading APIs.
- Write adapter tests that cover mid-object cursor positions to catch misuse early.
When it happens
Trigger: Calling a TypeAdapter that internally invokes JsonReader-based nextJsonElement() while positioned on a NAME token (i.e. expecting a value but the cursor advanced only to the key), or after an array/object has been fully consumed (END_ARRAY/END_OBJECT), or at EOF (END_DOCUMENT). Most commonly triggered by custom adapters calling gson.fromJson(..., JsonElement.class) on a sub-tree, or by adapters that call peek()/skipValue() in the wrong order before requesting the element.
Common situations: Writing a custom TypeAdapterFactory whose read() peeks then conditionally delegates, but the peek advanced the cursor to a NAME; mixing hasNext()/nextName() with code that tries to grab the whole entry as a JsonElement; deserializing into Object or JsonElement from an already partially-consumed reader; bugs in streaming adapters that forget to call nextName() before reading the value.
Related errors
- Expected one JSON element but was {stack}
- Did not expect a name
- Please begin an object before writing a name.
- Incomplete document
- Unexpected token: " + peeked
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/201ebc3875fe2436.json.
Report an issue: GitHub.