Serialization
Source: Effective Java, 3rd Edition — Chapter 12 by Joshua Bloch (Items 85–87)
Java serialization encodes object graphs as bytes — and opens a large attack surface (gadget chains, deserialization bombs). These items: avoid it when you can, implement Serializable only with care, and design a logical serialized form.
Item 85: Prefer alternatives to Java serialization
Deserialization invokes readObject on types that implement Serializable — including library gadgets you never intended to run. Untrusted byte streams can mean RCE, DoS (deserialization bombs), or worse.
Winning move: don’t play — don’t deserialize untrusted data. For new systems use JSON, Protocol Buffers, or similar cross-platform structured data (attribute maps, not arbitrary object graphs).
If legacy serialization is unavoidable:
- Never accept untrusted streams (including careless RMI)
- Use
ObjectInputFilter(Java 9+) — prefer whitelisting over blacklisting - Filters limit class/memory depth but won’t stop all bombs
Alternatives to Java serialization
1 / 2New microservice exchanging data with a browser and a Go backend
Pick an answer to see if you are right.
Item 86: Implement Serializable with great caution
implements Serializable is a long-term API commitment: the serialized form becomes exported forever. Default serialization exposes private fields and ties you to the physical representation.
Costs:
- Evolution — missing
serialVersionUIDbreaks compatibility when the class changes - Security — deserialization is a hidden constructor (Item 88)
- Testing — round-trip across releases
Declare explicitly:
private static final long serialVersionUID = 1L;
Inner classes should not implement Serializable (ill-defined default form). Static member classes may. Classes for inheritance rarely should implement Serializable.
Implementing Serializable
1 / 2Non-static inner class implements Serializable
Pick an answer to see if you are right.
Item 87: Consider using a custom serialized form
Default serialized form mirrors the physical object graph — often wrong when representation ≠ logical state (linked lists, hash tables).
Prefer custom writeObject / readObject emitting logical data only. Mark implementation fields transient. Still call defaultWriteObject/defaultReadObject first (even if all fields are transient) for forward compatibility.
Document serialized fields with @serial and custom writers with @serialData. Declare serialVersionUID.
Items 85–87 map
| Item | Move |
|---|---|
| 85 | JSON/protobuf; never deserialize untrusted bytes |
| 86 | Serializable is a commitment; serialVersionUID; no inner classes |
| 87 | Custom form = logical state; transient implementation fields |
Next: Safe Deserialization (Items 88–90).