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
- 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.
- If you control the Number subclass, ensure toString() emits canonical JSON number syntax (optional sign, digits, single optional dot, optional exponent).
- Sanitize/normalize the string with a regex and use writer.jsonValue(canonicalString) only after confirming it matches the JSON number grammar.
- 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
- Convert third-party/Money/Decimal Number types to BigDecimal or double at the boundary, not at serialization time.
- If you implement a Number subclass, make toString() emit canonical JSON number syntax.
- Avoid forwarding LazilyParsedNumber read from one reader onto another writer; convert first.
- Strip Locale-aware grouping separators and currency symbols before relying on toString().
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
- Numeric values must be finite, but was " + string
- JSON forbids NaN and infinities: {value}
- JSON must have only one top-level value.
- String contains non-ASCII characters: {s}{location}
- Expecting number, got: " + jsonToken + "; at path " + in.get
AI-assisted analysis of google/gson@8b8628c656 (2026-08-04).
Data as JSON: /data/errors/e7598d264f02bdcc.json.
Report an issue: GitHub.