google/gson · error · NumberFormatException

Invalid number: {}

Error message

Invalid number: {}

What it means

Thrown by ISO8601Utils.parseInt when the FIRST character of a date/time numeric field is not a decimal digit (Character.digit returns < 0). parseInt parses substrings for year/month/day/hour/minute/second/fraction; a non-digit at the very first position triggers NumberFormatException("Invalid number: <substring>"), which ISO8601Utils rewraps as a ParseException.

Source

Thrown at gson/src/main/java/com/google/gson/internal/bind/util/ISO8601Utils.java:344

   * @param value the string to parse
   * @param beginIndex the start index for the integer in the string
   * @param endIndex the end index for the integer in the string
   * @return the int
   * @throws NumberFormatException if the value is not a number
   */
  private static int parseInt(String value, int beginIndex, int endIndex)
      throws NumberFormatException {
    if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) {
      throw new NumberFormatException(value);
    }
    // use same logic as in Integer.parseInt() but less generic we're not supporting negative values
    int i = beginIndex;
    int result = 0;
    int digit;
    if (i < endIndex) {
      digit = Character.digit(value.charAt(i++), 10);
      if (digit < 0) {
        throw new NumberFormatException("Invalid number: " + value.substring(beginIndex, endIndex));
      }
      result = -digit;
    }
    while (i < endIndex) {
      digit = Character.digit(value.charAt(i++), 10);
      if (digit < 0) {
        throw new NumberFormatException("Invalid number: " + value.substring(beginIndex, endIndex));
      }
      result *= 10;
      result -= digit;
    }
    return -result;
  }

  /**
   * Zero pad a number to a specified length
   *
   * @param buffer buffer to use for padding

View on GitHub (pinned to 310ac341f2)

Solutions

  1. Validate the date string against an ISO8601 regex before parsing (e.g. ^\d{4}-\d{2}-\d{2}T...).
  2. Fix the producer to emit well-formed ISO8601 with zero-padded numeric components.
  3. Register a custom TypeAdapter<Date> tolerant to the actual format you receive.
  4. Sanitize input (replace alternate separators, trim whitespace) before Gson.

Example fix

// before
// JSON: {"at":"2020-0a-01T00:00:00Z"} -> 'Invalid number: 0a'

// after: validate at boundary
String s = jsonValue;
if (!s.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2})")) {
  throw new IllegalArgumentException("not ISO8601: " + s);
}
Date d = gson.fromJson(json, MyType.class).at;
Defensive patterns

Strategy: validation

Validate before calling

private static final Pattern ISO =
  Pattern.compile("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$");
String raw = jsonNode.get("at").getAsString();
if (raw == null || !ISO.matcher(raw).matches()) {
  throw new IllegalArgumentException("Malformed ISO8601 date (non-digit component): " + raw);
}

Try / catch

try {
  return gson.fromJson(json, Event.class);
} catch (JsonSyntaxException e) {
  if (e.getCause() instanceof ParseException && e.getCause().getMessage().contains("Invalid number")) {
    throw new IllegalArgumentException("Corrupt date field", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: A date component that should be numeric contains a non-digit at its start: e.g. month value starting with a letter, a separator character where a digit is expected, or an off-by-one parse where the position lands on a delimiter. Example: "2020-0a-01" or a malformed date where offsets into the string are wrong.

Common situations: Truncated date strings (too short, so offset walks into next field's delimiter); corrupted log timestamps; locale-specific separators ('/' instead of '-'); producers that left-pad with spaces instead of zeros; encoding issues inserting non-ASCII digits.

Related errors


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