All topics
library
intermediate

Serialization & Deserialization

Understand Java's built-in serialization, serialVersionUID, transient, and the security concerns.

Serialization turns an object graph into bytes. The class must implement Serializable.

Serialization = freeze-drying food (preserving state for transport). Deserialization = rehydrating it. But someone could tamper with the freeze-dried packet (untrusted data = security risk).

Key Concepts

1
    class Order implements Serializable {
      private static final long serialVersionUID = 1L;
      private String id;
      private transient String cardNumber;     // excluded
    }
2
    try (var out = new ObjectOutputStream(new FileOutputStream("o.ser"))) {
      out.writeObject(order);
    }
3
Always declare serialVersionUID yourself. Leave it out and the compiler derives one from the class structure — so adding a field changes it, and reading old data then throws InvalidClassException.
serialVersionUIDInvalidClassException
4
The rules.
    transient fields    skipped; come back as null / 0 / false
    static fields       never written -- they belong to the class
    the whole graph     every referenced object must also be
                        Serializable, or NotSerializableException
5
Deserialization does not call your constructor. The JVM allocates the object and sets fields by reflection, so every validation you wrote is bypassed:
6
    Order(String id) {
      if (id == null) throw new IllegalArgumentException();    // never runs
    }
7
Which is the root of the security problem. Deserialising untrusted bytes lets an attacker construct arbitrary objects in your JVM, and chains of ordinary library classes have been used to reach remote code execution.
8
    // Java 9+: restrict what may be deserialised
    ObjectInputFilter.Config.setSerialFilter(
      ObjectInputFilter.Config.createFilter("com.shop.*;!*"));
9
So do not use Java serialization for anything external. JSON or protobuf for data crossing a boundary; Java serialization at most between processes you control, behind a filter.
10
The customisation hooks, for the cases where you must.
11
    writeObject / readObject    custom format for one class
    writeReplace                swap in another object when writing
    readResolve                 swap in another after reading --
                                this is how a singleton stays single
    Externalizable              full manual control
12
readResolve is the one to remember. Without it, deserialising a singleton creates a second instance and quietly breaks the guarantee. An enum singleton has no such problem.
readResolve
13
What the interviewer is probing.1. "Why is Java serialization considered dangerous?" Probing: the security argument. Stalls: "It is just old." Moves up: deserialisation constructs objects without calling constructors, so validation is bypassed, and gadget chains in ordinary libraries have reached remote code execution.
14
2. "What does serialVersionUID do and why declare it?" Probing: versioning. Stalls: "It is a version number." Moves up: it identifies the class version; omit it and the compiler derives one from the structure, so adding a field makes old data unreadable with InvalidClassException.
15
3. "How do you preserve a singleton across deserialisation?" Probing: readResolve. Stalls: "It just works." Moves up: implement readResolve to return the existing instance — otherwise deserialising creates a second one. An enum singleton has no such problem.
16
4. "What should you use instead for data crossing a boundary?" Probing: whether you reach the practical conclusion. Stalls: "Java serialization is fine internally." Moves up: JSON or protobuf, with Java serialization at most between processes you control and behind an ObjectInputFilter.