google/gson · error · IllegalStateException
Expected STRING but was ${token} at path ${path}
Error message
Expected STRING but was ${token} at path ${path} What it means
JsonTreeReader.nextString accepts only STRING or NUMBER tokens; anything else throws IllegalStateException with the actual token and path. Used internally by adapters that call nextString expecting text.
Source
Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java:210
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);
}
@Override
public String nextString() throws IOException {
JsonToken token = peek();
if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
throw new IllegalStateException(
"Expected " + JsonToken.STRING + " but was " + token + locationString());
}
String result = ((JsonPrimitive) popStack()).getAsString();
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
return result;
}
@Override
public boolean nextBoolean() throws IOException {
expect(JsonToken.BOOLEAN);
boolean result = ((JsonPrimitive) popStack()).getAsBoolean();
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
}
return result;
}View on GitHub (pinned to 310ac341f2)
Solutions
- Make the producer emit a string for that field ("true" rather than true).
- Register a custom TypeAdapter that reads the raw token regardless of type.
- Use a more tolerant reader (setLenient) only if the data genuinely varies, but prefer fixing the schema.
Example fix
// before: {"flag":true} into String field
Gson().fromJson("{\"flag\":true}", FlagHolder::class.java) // Expected STRING but was BOOLEAN
// after: {"flag":"true"}
Gson().fromJson("{\"flag\":\"true\"}", FlagHolder::class.java) Defensive patterns
Strategy: validation
Validate before calling
// Inspect token before nextString
JsonToken t = reader.peek();
if (t == STRING || t == NUMBER) {
String s = reader.nextString();
} else {
reader.skipValue(); // or handle differently
} Try / catch
try {
reader.nextString();
} catch (IllegalStateException e) {
if (e.getMessage().startsWith("Expected STRING")) {
// coerce or skip the non-string token
reader.skipValue();
} else throw e;
} Prevention
- Keep String fields as JSON strings on the producer side.
- Register a tolerant TypeAdapter for fields that may receive other types.
- Peek before consuming when input shape is not guaranteed.
When it happens
Trigger: An adapter calls nextString() (often via a String-typed field) but the JSON value is a boolean, null, object, or array. Typical when a field declared as String actually receives a non-string literal.
Common situations: Producer changing a string field to a boolean/number; loose typing where a String field receives 'true'; deserializing JsonElement-backed trees where a primitive is not a string.
Related errors
- Expected NUMBER but was ${token} at path ${path}
- Expected ${expected} but was ${peek} at path ${path}
- JsonReader is closed
- Custom JsonElement subclass ${className} is not supported
- JSON forbids NaN and infinities: ${result}
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/ed5f946b86330a5c.
Report an issue: GitHub.