google/gson · error · InvalidObjectException

Deserialization is unsupported

Error message

Deserialization is unsupported

What it means

LazilyParsedNumber is a Gson-internal number holder that is never meant to be reconstructed via Java serialization. Its writeReplace() emits a BigDecimal so other JVMs need Gson on neither side; its readObject() throws InvalidObjectException to prevent creating an instance in an inconsistent state. Triggered only by circumventing writeReplace, e.g. via a malicious or buggy custom ObjectInputStream.

Solutions

  1. Do not serialize LazilyParsedNumber directly; let writeReplace emit a BigDecimal and deserialize that instead.
  2. If you must move parsed numbers across serialization, convert to BigDecimal/Long/String before writing: new BigDecimal(lpn.toString()).
  3. Re-examine any custom ObjectInputStream subclass that overrides resolveClass/readClassDescriptor and may be selecting the original class over its replacement.

Example fix

// before
oos.writeObject(gsonParsedNumber); // later fails if stream is tampered
// after
oos.writeObject(new BigDecimal(gsonParsedNumber.toString()));
Defensive patterns

Strategy: validation

Validate before calling

// never attempt to deserialize LazilyParsedNumber directly
Object toSerialize = (n instanceof LazilyParsedNumber) ? new BigDecimal(n.toString()) : n;

Type guard

static Object safeSerialize(Number n) {
  return (n instanceof com.google.gson.internal.LazilyParsedNumber) ? new java.math.BigDecimal(n.toString()) : n;
}

Try / catch

try (ObjectInputStream ois = new ObjectInputStream(in)) {
  Object o = ois.readObject();
} catch (InvalidObjectException e) {
  // stream bypassed writeReplace; re-read expecting BigDecimal
}

Prevention

When it happens

Trigger: An ObjectInputStream directly deserializing a stream whose class descriptor names LazilyParsedNumber (bypassing or overriding writeReplace), reaching LazilyParsedNumber.readObject at LazilyParsedNumber.java:88.

Common situations: Custom deserialization harnesses; malformed/manipulated serialized streams; tests that mock serialization of Gson's internal types; transferring Gson-parsed numbers across an RMI or ObjectOutputStream boundary and then attempting to read them back without the writeReplace replacement.

Related errors


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

Appendix: source

Thrown at gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java:91

  }

  @Override
  public String toString() {
    return value;
  }

  /**
   * If somebody is unlucky enough to have to serialize one of these, serialize it as a BigDecimal
   * so that they won't need Gson on the other side to deserialize it.
   */
  private Object writeReplace() {
    return asBigDecimal();
  }

  private void readObject(ObjectInputStream in) throws IOException {
    // Don't permit directly deserializing this class; writeReplace() should have written a
    // replacement
    throw new InvalidObjectException("Deserialization is unsupported");
  }

  /**
   * Compares this LazilyParsedNumber with the specified LazilyParsedNumber. The comparison is
   * lexicographical, based on the string values of the two numbers, so it does not in general
   * correspond to numeric comparison. For numeric comparison, call {@link #asBigDecimal()} on both
   * numbers and compare the results.
   */
  @Override
  public int compareTo(LazilyParsedNumber other) {
    return value.compareTo(other.value);
  }

  @Override
  public int hashCode() {
    return value.hashCode();
  }

View on GitHub (pinned to 310ac341f2)