google/gson · error · IllegalArgumentException

String created by {} is not a valid JSON number: {}

Error message

String created by {} is not a valid JSON number: {}

What it means

Thrown by JsonWriter.value(Number) (JsonWriter.java:648-653) when the Number subclass is not one of the always-valid types (Integer, Long, Byte, Short, BigDecimal, BigInteger, AtomicInteger, AtomicLong) and its toString() does not match the VALID_JSON_NUMBER_PATTERN regex (-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][-+]?[0-9]+)?). This catches custom Number implementations whose string form would produce invalid JSON (leading zeros, hex, locale commas, trailing letters, etc.).

Source

Thrown at gson/src/main/java/com/google/gson/stream/JsonWriter.java:651

  public JsonWriter value(Number value) throws IOException {
    if (value == null) {
      return nullValue();
    }

    writeDeferredName();
    String string = value.toString();
    Class<? extends Number> numberClass = value.getClass();

    if (!alwaysCreatesValidJsonNumber(numberClass)) {
      // Validate that string is valid before writing it directly to JSON output
      if (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN")) {
        if (strictness != Strictness.LENIENT) {
          throw new IllegalArgumentException("Numeric values must be finite, but was " + string);
        }
      } else if (numberClass != Float.class
          && numberClass != Double.class
          && !VALID_JSON_NUMBER_PATTERN.matcher(string).matches()) {
        throw new IllegalArgumentException(
            "String created by " + numberClass + " is not a valid JSON number: " + string);
      }
    }

    beforeValue();
    out.append(string);
    return this;
  }

  /**
   * Encodes {@code null}.
   *
   * @return this writer.
   */
  @CanIgnoreReturnValue
  public JsonWriter nullValue() throws IOException {
    if (deferredName != null) {
      if (serializeNulls) {

View on GitHub (pinned to 310ac341f2)

Solutions

  1. Convert the custom Number to a known-good type before writing: writer.value(number.doubleValue()) or writer.value(new BigDecimal(number.toString())).
  2. If you control the Number subclass, ensure toString() returns a strictly RFC-8259-compliant number.
  3. Validate the string against the JSON number grammar before calling value(Number).
  4. For non-finite cases, handle separately (see error 177).

Example fix

// before
writer.value(customNumber); // throws if toString() is "0x1A"

// after
writer.value(new BigDecimal(customNumber.toString()));
Defensive patterns

Strategy: validation

Validate before calling

String s = number.toString();
boolean valid = s.matches("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?");
if (valid) {
  writer.value(number);
} else {
  writer.value(new BigDecimal(s));
}

Type guard

private static final Pattern JSON_NUMBER =
    Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?");
static boolean isValidJsonNumberString(String s) {
  return JSON_NUMBER.matcher(s).matches();
}

Try / catch

try {
  writer.value(number);
} catch (IllegalArgumentException e) {
  writer.value(number.doubleValue()); // fall back to a known-good Number
}

Prevention

When it happens

Trigger: Passing a custom Number subclass (e.g. a LazyParsedNumber or domain-specific Number) whose toString() returns something like "0x1A", "1,000", "1.0e", "+5", or "01". Also triggered by a Number that returns "" or whitespace.

Common situations: Custom Number types for performance (lazy parsing, cached strings); third-party libraries exposing their own Number subclasses; locale-aware toString implementations; numbers carrying metadata in their string form.

Related errors


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