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
- Do not serialize LazilyParsedNumber directly; let writeReplace emit a BigDecimal and deserialize that instead.
- If you must move parsed numbers across serialization, convert to BigDecimal/Long/String before writing: new BigDecimal(lpn.toString()).
- 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
- Do not persist Gson-internal Number types via Java serialization; convert to BigDecimal first.
- Treat LazilyParsedNumber as an internal type: never depend on its serialized form.
- Prefer JSON or explicit BigDecimal/Long for cross-process number transfer.
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
- Deserialization is unsupported
- Already wrote a name, expecting a value.
- Dangling name
- Please begin an object before writing a name.
- Accessor threw exception
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)