google/gson · error · IllegalStateException
Unexpected token: {peeked}
Error message
Unexpected token: {peeked} What it means
JsonElementTypeAdapter.readTerminal throws IllegalStateException('Unexpected token: ' + peeked) when the reader is positioned at a structural token (BEGIN_ARRAY, BEGIN_OBJECT, END_*, NAME, END_DOCUMENT) at a point where only a terminal value (STRING, NUMBER, BOOLEAN, NULL) is expected. This signals the JsonReader is in an invalid state for reading a single JsonElement value, typically because the caller advanced the reader incorrectly before delegating to JsonElementTypeAdapter. The {peeked} is the offending JsonToken.
Source
Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonElementTypeAdapter.java:72
}
}
/** Reads a {@link JsonElement} which cannot have any nested elements */
private JsonElement readTerminal(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) {
case STRING:
return new JsonPrimitive(in.nextString());
case NUMBER:
String number = in.nextString();
return new JsonPrimitive(new LazilyParsedNumber(number));
case BOOLEAN:
return new JsonPrimitive(in.nextBoolean());
case NULL:
in.nextNull();
return JsonNull.INSTANCE;
default:
// When read(JsonReader) is called with JsonReader in invalid state
throw new IllegalStateException("Unexpected token: " + peeked);
}
}
@Override
public JsonElement read(JsonReader in) throws IOException {
// Optimization if value already exists as JsonElement
if (in instanceof JsonTreeReader) {
return ((JsonTreeReader) in).nextJsonElement();
}
// Either JsonArray or JsonObject
JsonElement current;
JsonToken peeked = in.peek();
current = tryBeginNesting(in, peeked);
if (current == null) {
return readTerminal(in, peeked);
}View on GitHub (pinned to 8b8628c656)
Solutions
- Ensure the JsonReader is positioned at the start of a value (STRING/NUMBER/BOOLEAN/NULL/BEGIN_ARRAY/BEGIN_OBJECT) before reading a JsonElement.
- Do not interleave manual beginObject()/nextName() with JsonElement reads on the same reader — let one consumer own the cursor.
- Create a fresh JsonReader for each parse, or rewind/recreate the source if you must re-read.
- In a custom adapter, call in.peek() and handle structural tokens explicitly instead of delegating blindly.
Example fix
// before
JsonReader r = new JsonReader(new StringReader("{\"a\":1}"));
r.beginObject(); r.nextName();
JsonElement e = JsonParser.parseReader(r); // positioned at NUMBER inside object -> IllegalStateException
// after: parse the whole document, then navigate the tree
JsonElement e = JsonParser.parseString("{\"a\":1}");
int a = e.getAsJsonObject().get("a").getAsInt(); Defensive patterns
Strategy: validation
Validate before calling
// Ensure the reader is positioned at a value token before reading a JsonElement
JsonToken t = reader.peek();
if (t == JsonToken.NAME || t == JsonToken.END_ARRAY
|| t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT
|| t == JsonToken.BEGIN_ARRAY || t == JsonToken.BEGIN_OBJECT) {
// BEGIN_ARRAY/BEGIN_OBJECT are fine for JsonElement; NAME/END_*/END_DOCUMENT are not
}
if (t == JsonToken.NAME || t == JsonToken.END_ARRAY
|| t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT) {
throw new IllegalStateException("Reader not at a value: " + t);
} Try / catch
try {
return JsonParser.parseReader(reader);
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Unexpected token: ")) {
throw new IllegalStateException("Reader misaligned before JsonElement read at " + reader.getPath(), e);
}
throw e;
} Prevention
- Do not interleave manual token reading with JsonElement parsing on one reader.
- Prefer parsing the whole document into a tree, then navigating.
- Create a fresh JsonReader per parse operation.
When it happens
Trigger: Calling JsonParser.parseReader(JsonReader) (or reading a JsonElement) after manually consuming part of the stream so the cursor sits on a NAME or END token; reusing a JsonReader past its logical end; mixing manual nextName()/beginObject() with JsonElement parsing on the same reader.
Common situations: Custom TypeAdapter that does some manual token reading then calls gson.getAdapter(JsonElement.class).read(in) at the wrong position; streaming parser reuse; tests that hand a JsonReader to multiple consumers.
Related errors
- Unexpected {peeked} when reading a JsonElement.
- Failed parsing JSON source to Json
- Couldn't write {class}
- Custom JsonElement subclass {className} is not supported
- Expected one JSON element but was {stack}
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/7938f819cc88dffd.json.
Report an issue: GitHub.