Safe Deserialization
Source: Effective Java, 3rd Edition — Chapter 12 by Joshua Bloch (Items 88–90)
This lesson closes Chapter 12 — and the Effective Java intermediate track: defensive readObject, enum singletons over readResolve, and the serialization proxy pattern.
Item 88: Write readObject methods defensively
readObject is a public constructor that takes a byte stream. Attackers can craft streams that violate invariants or steal references to internal mutable state (the Period/Date attacks).
Treat readObject like a constructor (Items 49–50):
- Defensive-copy mutable components (before validation)
- Validate invariants — throw
InvalidObjectExceptionon failure - Never call overridable methods during deserialization
Litmus test: would you add a public constructor that assigns every nontransient field with no checks? If not, fix readObject — or use a serialization proxy (Item 90).
Defensive readObject
1 / 2Immutable serializable class with private mutable Date fields
Pick an answer to see if you are right.
Item 89: For instance control, prefer enum types to readResolve
readResolve can swap the deserialized instance for a canonical one (e.g. singleton INSTANCE). It is fragile: nontransient reference fields can be “stolen” before readResolve runs (Elvis stealer attack).
Prefer a single-element enum for singletons (Item 3) — serialization guarantees constant identity without readResolve.
If instances aren’t known at compile time, readResolve may be necessary — make reference fields transient and use private on final classes.
Instance control
1 / 2Serializable singleton with readResolve and nontransient String[] field
Pick an answer to see if you are right.
Item 90: Consider serialization proxies instead of serialized instances
The serialization proxy pattern:
private staticnestedSerializationProxycopies logical statewriteReplace()on the enclosing class returns the proxyreadObjecton the enclosing class throwsInvalidObjectException(blocks fake instances)readResolve()on the proxy rebuilds via public API (constructors/factories)
Benefits: final fields stay final, invariants enforced by normal construction, attacks from Items 88–89 blocked. Cost: ~14% overhead vs defensive readObject in Bloch’s tests. Not for user-extendable classes or graphs with circularities during readResolve.
Items 88–90 map
| Item | Move |
|---|---|
| 88 | readObject = constructor: copy, validate, no overridables |
| 89 | enum singleton beats readResolve |
| 90 | Proxy: writeReplace, reject readObject, proxy readResolve |
Chapter 12 complete. Effective Java intermediate track complete (Items 1–90).