google/gson · error · IllegalStateException

Expected NUMBER but was {token}{location}

Error message

Expected NUMBER but was {token}{location}

What it means

JsonTreeReader.nextDouble throws IllegalStateException('Expected NUMBER but was ' + token + locationString) when the current token is neither NUMBER nor STRING. nextDouble accepts STRING as a convenience (it parses the string to double), but rejects BOOLEAN, NULL, NAME, BEGIN_*, END_*. The {token} is the actual JsonToken, {location} the path.

Source

Thrown at gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java:243

      pathIndices[stackSize - 1]++;
    }
    return result;
  }

  @Override
  public void nextNull() throws IOException {
    expect(JsonToken.NULL);
    popStack();
    if (stackSize > 0) {
      pathIndices[stackSize - 1]++;
    }
  }

  @Override
  public double nextDouble() throws IOException {
    JsonToken token = peek();
    if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
      throw new IllegalStateException(
          "Expected " + JsonToken.NUMBER + " but was " + token + locationString());
    }
    JsonPrimitive primitive = (JsonPrimitive) peekStack();
    double result;
    try {
      result = primitive.getAsDouble();
    } catch (NumberFormatException e) {
      throw numberFormatException("Expected a double but was " + primitive.getAsString(), e);
    }
    if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) {
      throw new MalformedJsonException("JSON forbids NaN and infinities: " + result);
    }
    popStack();
    if (stackSize > 0) {
      pathIndices[stackSize - 1]++;
    }
    return result;
  }

View on GitHub (pinned to 8b8628c656)

Solutions

  1. Branch on in.peek() before nextDouble(): handle NULL with nextNull(), handle STRING with explicit parse if allowed, reject other tokens.
  2. If the field can legitimately be null, make the Java field a boxed Double and read null explicitly.
  3. Validate the JSON shape before deserializing, or use a JsonElement capture +getAsDouble() with your own error handling.
  4. Fix the producer to emit the numeric value consistently.

Example fix

// before
double v = in.nextDouble(); // throws if token is NULL

// after
Double v;
if (in.peek() == JsonToken.NULL) { in.nextNull(); v = null; }
else { v = in.nextDouble(); }
Defensive patterns

Strategy: type-guard

Validate before calling

JsonToken t = reader.peek();
if (t != JsonToken.NUMBER && t != JsonToken.STRING) {
  throw new IllegalStateException("Expected number at " + reader.getPath() + ", got " + t);
}
double v = reader.nextDouble();

Type guard

static boolean isNumberOrStringToken(JsonReader r) throws IOException {
  JsonToken t = r.peek();
  return t == JsonToken.NUMBER || t == JsonToken.STRING;
}

Try / catch

try {
  return reader.nextDouble();
} catch (IllegalStateException e) {
  if (e.getMessage() != null && e.getMessage().startsWith("Expected NUMBER")) {
    throw new InvalidPayloadException("Expected numeric field at " + reader.getPath(), e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling nextDouble() when the reader sits on a boolean, null, object, or array. Common when deserializing a numeric field whose value is missing (NULL) or accidentally encoded as a nested object.

Common situations: Numeric field changed to nullable in a new API version (null instead of 0); field swapped from number to boolean flag; wrong key being read so the cursor lands on a structural token.

Related errors


AI-assisted analysis of google/gson@8b8628c656 (2026-08-04). Data as JSON: /data/errors/1c4cfb8b100e2b24.json. Report an issue: GitHub.