google/gson · error · NumberFormatException

Expected a long but was {}{}

Error message

Expected a long but was {}{}

What it means

Thrown by JsonReader.nextLong() (JsonReader.java:1124-1127) when the literal was read as a string, parsed as a double, and casting back to long changed the value (result != asDouble). This means the JSON number had a fractional part or magnitude that cannot be represented exactly as a long. It is a NumberFormatException, not an IOException, to signal a value-conversion failure rather than a malformed stream.

Source

Thrown at gson/src/main/java/com/google/gson/stream/JsonReader.java:1126

        // Fall back to parse as a double below.
      }
    } else {
      throw unexpectedTokenError("a long");
    }

    peeked = PEEKED_BUFFERED;
    double asDouble;
    try {
      asDouble = Double.parseDouble(peekedString);
    } catch (NumberFormatException e) {
      NumberFormatException rethrown =
          new NumberFormatException("Expected a long but was " + peekedString + locationString());
      rethrown.initCause(e);
      throw rethrown;
    }
    long result = (long) asDouble;
    if (result != asDouble) { // Make sure no precision was lost casting to 'long'.
      throw new NumberFormatException("Expected a long but was " + peekedString + locationString());
    }
    peekedString = null;
    peeked = PEEKED_NONE;
    pathIndices[stackSize - 1]++;
    return result;
  }

  /**
   * Returns the string up to but not including {@code quote}, unescaping any character escape
   * sequences encountered along the way. The opening quote should have already been read. This
   * consumes the closing quote, but does not include it in the returned string.
   *
   * @param quote either ' or ".
   */
  private String nextQuotedValue(char quote) throws IOException {
    // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
    char[] buffer = this.buffer;
    StringBuilder builder = null;

View on GitHub (pinned to 310ac341f2)

Solutions

  1. Use nextDouble() instead of nextLong() if the value may be fractional.
  2. Validate with peek()==JsonToken.NUMBER and pre-check the literal before committing to nextLong.
  3. Round or truncate explicitly: Math.round(reader.nextDouble()).
  4. Align the JSON producer to emit integers for integral fields.

Example fix

// before
long id = reader.nextLong(); // fails on "3.14"

// after
long id = (long) reader.nextDouble();
Defensive patterns

Strategy: validation

Validate before calling

if (reader.peek() == JsonToken.STRING || reader.peek() == JsonToken.NUMBER) {
  String raw = reader.nextString();
  long v = new BigDecimal(raw).setScale(0, RoundingMode.DOWN).longValueExact();
}
// or simply widen the field:
long v = (long) reader.nextDouble();

Type guard

static boolean isExactLong(String literal) {
  try { Long.parseLong(literal); return true; }
  catch (NumberFormatException e) { return false; }
}

Try / catch

try {
  return reader.nextLong();
} catch (NumberFormatException e) {
  return (long) reader.nextDouble(); // best-effort, only if appropriate
}

Prevention

When it happens

Trigger: Calling nextLong() on a JSON value like 3.14, 1e10 (lossy), or a string "3.5". The reader first tries Long.parseLong; on failure it falls back to Double.parseDouble and then checks that (long)asDouble == asDouble, throwing here when it does not.

Common situations: API that returns numbers as decimals or strings in JSON; switching a field from nextDouble to nextLong without verifying the producer; locale-agnostic stringified numbers; mixed schemas where a field is sometimes integral and sometimes a float.

Related errors


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