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

  1. Make the producer emit a string for that field ("true" rather than true).
  2. Register a custom TypeAdapter that reads the raw token regardless of type.
  3. 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

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


AI-assisted analysis of google/gson@310ac341f2 (2026-08-10). Data as JSON: /api/errors/ed5f946b86330a5c. Report an issue: GitHub.