google/gson · error · IllegalStateException
Expected {expected} but was {peek}{location}
Error message
Expected {expected} but was {peek}{location} What it means
JsonTreeReader.expect throws IllegalStateException('Expected ' + expected + ' but was ' + peek() + locationString) when the current token does not match the structural token the method requires. expect() is called by beginArray/endArray/beginObject/endObject/nextBoolean/nextNull, so any mismatch between what the caller asks for (e.g. beginArray) and what the tree actually has (e.g. an object) is rejected. The {expected} is the required JsonToken, {peek} the actual token, {location} the JSON path.
Source
Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java:186
throw new MalformedJsonException(
"Custom JsonElement subclass " + o.getClass().getName() + " is not supported");
}
}
private Object peekStack() {
return stack[stackSize - 1];
}
@CanIgnoreReturnValue
private Object popStack() {
Object result = stack[--stackSize];
stack[stackSize] = null;
return result;
}
private void expect(JsonToken expected) throws IOException {
if (peek() != expected) {
throw new IllegalStateException(
"Expected " + expected + " but was " + peek() + locationString());
}
}
private String nextName(boolean skipName) throws IOException {
expect(JsonToken.NAME);
Iterator<?> i = (Iterator<?>) peekStack();
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
String result = (String) entry.getKey();
pathNames[stackSize - 1] = skipName ? "<skipped>" : result;
push(entry.getValue());
return result;
}
@Override
public String nextName() throws IOException {
return nextName(false);
}View on GitHub (pinned to 8b8628c656)
Solutions
- Inspect the {expected}/{peek}/{location} in the message to find where the layout assumption diverges from the data.
- In custom adapters, branch on in.peek() rather than assuming a fixed token sequence.
- Validate the JSON structure (object vs array at each path) before deserializing, or use lenient manual navigation.
- Align the producer's data shape with the adapter, or update the adapter to match the new shape.
Example fix
// before: adapter assumes array but data is object
class Adapter extends TypeAdapter<List<X>> {
public List<X> read(JsonReader in) throws IOException {
in.beginArray(); // IllegalStateException if JSON is an object
...
}
}
// after: branch on token
public List<X> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.BEGIN_OBJECT) { /* read as map */ }
else { in.beginArray(); /* read as list */ }
...
} Defensive patterns
Strategy: type-guard
Validate before calling
// Check the next token matches what your adapter assumes before calling begin*/end*
JsonToken t = reader.peek();
if (t != JsonToken.BEGIN_OBJECT) {
throw new IllegalStateException("Expected object at " + reader.getPath() + ", got " + t);
}
reader.beginObject(); Type guard
static boolean isToken(JsonReader r, JsonToken expected) throws IOException {
return r.peek() == expected;
} Try / catch
try {
reader.beginArray();
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Expected ")) {
throw new InvalidPayloadException("Structure mismatch at " + reader.getPath(), e);
}
throw e;
} Prevention
- Branch on peek() rather than assuming a fixed token layout.
- Keep data contracts explicit and versioned.
- Add round-trip tests for the JSON shape your adapters expect.
When it happens
Trigger: Calling beginArray() when the current element is a JsonObject (or vice versa); endObject() when there is no open object; nextBoolean()/nextNull() when the token is not BOOLEAN/NULL. Typical of a custom TypeAdapter that assumes a layout that does not match the data.
Common situations: Custom adapter with hard-coded structure assumptions; data contract drift; wrong key ordering causing nextName misalignment; reusing a reader that's mid-structure.
Related errors
- Expected STRING but was {token}{location}
- Expected NUMBER but was {token}{location}
- JsonReader is closed
- JSON forbids NaN and infinities: {result}
- GSON ({GsonBuildConfig.VERSION}) cannot handle {type}
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/4814083cb77b40cc.json.
Report an issue: GitHub.