google/gson · error · IllegalStateException
Unexpected token: ${peeked}
Error message
Unexpected token: ${peeked} What it means
ObjectTypeAdapter.readTerminal() only accepts STRING, NUMBER, BOOLEAN, and NULL. For any other token (NAME, END_ARRAY, END_OBJECT, END_DOCUMENT) it throws IllegalStateException("Unexpected token: ..."). The comment in source notes this happens 'when read(JsonReader) is called with JsonReader in invalid state' - typically a bug in a custom adapter that mis-advances the reader before delegating to Object deserialization, rather than malformed user JSON per se.
Source
Thrown at gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java:104
return null;
}
}
/** Reads an {@code Object} which cannot have any nested elements */
private Object readTerminal(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) {
case STRING:
return in.nextString();
case NUMBER:
return toNumberStrategy.readNumber(in);
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
default:
// When read(JsonReader) is called with JsonReader in invalid state
throw new IllegalStateException("Unexpected token: " + peeked);
}
}
@Override
public Object read(JsonReader in) throws IOException {
// Either List or Map
Object current;
JsonToken peeked = in.peek();
current = tryBeginNesting(in, peeked);
if (current == null) {
return readTerminal(in, peeked);
}
Deque<Object> stack = new ArrayDeque<>();
while (true) {
while (in.hasNext()) {View on GitHub (pinned to 310ac341f2)
Solutions
- Ensure the JsonReader is positioned at a value token before delegating to Object deserialization
- Do not call nextName()/beginObject() and then pass the reader to ObjectTypeAdapter without proper positioning
- Validate JSON structure (or unit-test the adapter) to confirm the reader state at delegation points
Defensive patterns
Strategy: validation
Validate before calling
// Before delegating to Object deserialization, ensure the reader is at 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("Reader not positioned at a value: " + t);
}
Object o = gson.getAdapter(Object.class).read(reader); Try / catch
try {
Object o = gson.fromJson(reader, Object.class);
} catch (IllegalStateException e) {
// 'Unexpected token: <token>': reader was at a structural token; reposition before retry
} Prevention
- Always leave the JsonReader positioned at a value before delegating to Object deserialization
- Do not interleave nextName()/beginObject() with Object adapter delegation
- Unit-test custom adapters to confirm reader state at handoff points
When it happens
Trigger: A custom TypeAdapter that calls nextName()/beginObject() and then delegates the reader to Object deserialization without leaving it positioned at a value; calling fromJson where the reader is at a structural token rather than a value; misusing the reader API in streaming code.
Common situations: Custom adapter that consumes the wrong tokens; incorrect manual JsonReader usage; a TypeAdapter that partially reads an object then hands off to Gson's Object adapter.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Expecting number, got: ${jsonToken}; at path ${path}
- String contains non-ASCII characters: ${s} at path ${path}
- duplicate key: ${key}
- Failed to invoke constructor '${constructor}' with args ${ac
- cannot deserialize ${baseType} because it does not define a
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/5a03680c38e8b5cd.
Report an issue: GitHub.