What do readResolve and writeReplace do, and why are they essential for serializing singletons?
answer
- writeReplace: substitute on the way OUT (before writing)
- readResolve: substitute on the way IN (after reading) — its return value wins
- Singletons: readResolve returns INSTANCE so no duplicate is created
- Serialization proxy pattern = writeReplace + proxy.readResolve via real constructor
- Single-element enum is the safest serializable singleton
basics
~20 sThey let a class swap one object for another during serialization. writeReplace replaces the object being written; readResolve replaces the object just produced by deserialization. For a singleton, readResolve returns the existing single instance so deserialization doesn't create a duplicate.
solid answer
~50 swriteReplace and readResolve are optional hooks that SUBSTITUTE instances. writeReplace (Object writeReplace()) is called before writing and lets the object emit a different stand-in object to the stream — often a compact serialization proxy. readResolve (Object readResolve()) is called after an object is fully deserialized and lets you replace it with another object; whatever it returns becomes the result of readObject. The classic problem they solve: plain deserialization always creates a NEW object, which silently breaks the singleton guarantee — you'd end up with two 'unique' instances. By implementing readResolve to return the canonical INSTANCE, every deserialization yields the same object. They also power the serialization proxy pattern: writeReplace emits a small proxy holding logical state, and the proxy's readResolve reconstructs the real object through its normal constructor, sidestepping the constructor-bypass attacks. Note: for true singletons, a single-element enum is even safer because the JVM guarantees enum serialization preserves identity automatically.
go deeper
Knows readResolve helps keep singletons single after deserialization.
Can write readResolve returning INSTANCE and explains that deserialization otherwise creates a new object.
Distinguishes writeReplace (outbound) from readResolve (inbound), implements the serialization proxy pattern, and knows transient fields guard against stolen-reference attacks.
Recommends single-element enums for singletons, reasons about the proxy pattern's security/immutability benefits, and treats deserialization as untrusted input across the whole object graph.
## The substitution hooks Most serialization hooks customize HOW an object's fields move to/from the stream. `writeReplace` and `readResolve` are different: they let a class hand the serialization machinery a COMPLETELY DIFFERENT object. ```java Object writeReplace() throws ObjectStreamException; // before writing Object readResolve() throws ObjectStreamException; // after reading ``` (They may be any access level, but are commonly `private` or `protected`; like the other hooks they're invoked reflectively.) - **`writeReplace`** is consulted when you serialize an object. If the class defines it, the runtime calls it and serializes the RETURNED object instead of the original. Example: an object returns a lightweight 'proxy' that captures only its logical state. - **`readResolve`** is consulted after an object has been fully read back from the stream (after `readObject`/`readExternal` completes). Whatever it returns becomes the final result handed to the caller; the just-deserialized object is discarded. ## Why singletons break without them A **singleton** is a class guaranteed to have exactly one instance (e.g. `INSTANCE`). Deserialization, by design, **constructs a brand-new object** from the byte stream — it does not consult your `getInstance()` or your static field. So if a singleton is serializable, serializing then deserializing it produces a SECOND object that is `!=` the real `INSTANCE`. Now `==` comparisons fail and any 'there can be only one' invariant is violated. This is a classic, subtle bug. `readResolve` fixes it: ```java public class Registry implements Serializable { public static final Registry INSTANCE = new Registry(); private Registry() {} private Object readResolve() { return INSTANCE; // discard the deserialized copy, return the canonical one } } ``` Now every deserialization returns the one true `INSTANCE`. ### A caveat for readResolve singletons If the singleton has any non-`transient` reference fields, an attacker can mount a 'stolen reference' attack: they capture the deserialized object before `readResolve` swaps it. The defense is to declare all such fields `transient`. (This subtlety is *why* a single-element enum is the recommended singleton — the JVM handles its serialization identity natively and immune to this attack.) ## The serialization proxy pattern (writeReplace + readResolve) This is the robust, recommended pattern for serializing complex immutable classes. The outer class never serializes itself directly: ```java class Period implements Serializable { private final Date start, end; // ... validating constructor ... private Object writeReplace() { return new Proxy(this); } // emit the proxy private void readObject(ObjectInputStream in) throws InvalidObjectException { throw new InvalidObjectException("Proxy required"); // forbid direct deserialization } private static class Proxy implements Serializable { private final Date start, end; Proxy(Period p) { this.start = p.start; this.end = p.end; } private Object readResolve() { return new Period(start, end); } // rebuild via real constructor } } ``` Why this is powerful: 1. The real object is reconstructed through its **normal validating constructor**, so all invariants hold — this defeats the constructor-bypass class of deserialization attacks. 2. The class can be `final` with `final` fields (no need to relax them for `readObject`). 3. The on-disk form is decoupled from the in-memory layout. ## Summary of who does what - `writeReplace` — outbound substitution (object → proxy/stand-in). - `readResolve` — inbound substitution (deserialized object → canonical/real object). - Together they implement singletons safely and the serialization proxy pattern; for plain singletons, prefer a single-element enum.
- Why is a single-element enum considered the best way to implement a serializable singleton?The JVM guarantees that enum constants are serialized and deserialized by name to the SAME instance, with no reflection or readResolve needed. This makes the singleton immune to both the duplicate-on-deserialize bug and reflection/stolen-reference attacks, with minimal code.
- How does the serialization proxy pattern defend against deserialization attacks?The outer class forbids direct deserialization (its readObject throws) and emits a small proxy via writeReplace. The proxy's readResolve rebuilds the real object through its normal validating constructor, so invariants are enforced and the constructor-bypass attack surface is removed.
saying these in an interview costs you the question
- Saying deserialization respects getInstance()/the static field (it always builds a new object)
- Confusing the two: readResolve writes, writeReplace reads (it's the reverse)
- Forgetting that singleton fields must be transient to avoid the stolen-reference attack
- Believing readObject alone preserves singleton identity (it does not — you need readResolve)