google/gson · error · IllegalArgumentException

String created by " + numberClass + " is not a valid JSON nu

Error message

String created by " + numberClass + " is not a valid JSON number: " + string

What it means

Thrown by JsonWriter.value(Number) when the argument is a Number subclass NOT in the trusted set (Integer, Long, Byte, Short, BigDecimal, BigInteger, AtomicInteger, AtomicLong) and NOT a Float/Double, and its toString() does not match Gson's VALID_JSON_NUMBER_PATTERN. Gson cannot assume an arbitrary Number's string form is JSON-safe, so it validates against the JSON number grammar and rejects anything malformed (e.g. leading zeros, hex, trailing junk, multiple dots). This protects downstream parsers from corrupt numeric tokens.

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 8b8628c656)

Solutions

  1. Convert the custom Number to a trusted type before writing: writer.value(number.doubleValue()) or writer.value(new BigDecimal(number.toString())) after you have validated the string.
  2. If you control the Number subclass, ensure toString() emits canonical JSON number syntax (optional sign, digits, single optional dot, optional exponent).
  3. Sanitize/normalize the string with a regex and use writer.jsonValue(canonicalString) only after confirming it matches the JSON number grammar.
  4. Strip locale formatting (grouping separators, currency symbols) before serialization.

Example fix

// before: Money extends Number, toString() => "$1,234.50"
writer.value(money);

// after
writer.value(money.getAmount()); // BigDecimal, a trusted type
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate a custom Number's toString against JSON number grammar before writing
private static final java.util.regex.Pattern JSON_NUMBER =
    java.util.regex.Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?");

public static boolean isJsonNumberString(Number n) {
  if (n == null) return false;
  String s = n.toString();
  return JSON_NUMBER.matcher(s).matches();
}

Type guard

// True if Gson trusts the Number subclass to always produce valid JSON numbers
public static boolean isTrustedNumberType(Number n) {
  if (n == null) return false;
  Class<?> c = n.getClass();
  return c == Integer.class || c == Long.class || c == Byte.class || c == Short.class
      || c == BigDecimal.class || c == BigInteger.class
      || c == AtomicInteger.class || c == AtomicLong.class;
}

Try / catch

try {
  writer.value(customNumber);
} catch (IllegalArgumentException e) {
  // fallback: emit as double, or as a string, or null
  writer.value(customNumber.doubleValue());
}

Prevention

When it happens

Trigger: Calling writer.value(new LazilyParsedNumber("0x1F")), writer.value(customNumberSubclass) whose toString() returns "1,000.0" or "1.2.3" or "$5", or any third-party Number (e.g. a library-specific Decimal/Money type) whose toString deviates from JSON number syntax. Also fires if a TypeAdapter boxes a numeric string into a custom Number and forwards it.

Common situations: Using Joda/Joda-Money, JavaFX Point2D-style Number wrappers, Kotlin unsigned types bridged to java.lang.Number, or internal LazilyParsedNumber read from one JsonReader and re-serialized on another writer; bugs where toString() includes grouping separators or currency symbols from a Locale-aware format.

Related errors


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