📁 I/O, Files & Networking · Intermediate

Serialization in Java

Serializable, transient, serialVersionUID, and deserialization security risks.

🧩 The mysteryJava can turn any object into bytes and back with two lines of code. Sounds perfect — so why did Java's own architects call it a "horrible mistake" they want to remove?

Freeze-drying objects

**ObjectOutputStream.writeObject turns an object — and everything it references — into bytes; ObjectInputStream.readObject rebuilds it. A class opts in by implementing Serializable, a marker interface** with no methods.

transient: skip this field

Fields marked **transient are not saved. On deserialization they come back as defaults** (null, 0, false), because Java doesn't run the class's constructor or field initializers when rebuilding the object.

class Session implements Serializable {
    String user;
    transient Socket conn; // not saved
}
🔮 Predict it

What survives the trip?

copy() serializes and deserializes in memory. What prints?

class Player implements Serializable {
    String name = "neo";
    transient int score = 99;
}
// copy() = serialize, then deserialize
Player p = copy(new Player());
System.out.println(p.name + " " + p.score);
  1. neo 99
  2. neo 0
  3. null 0
Show the answer

neo 0 — name was saved, but transient score wasn't, and the initializer = 99 doesn't run on deserialization, so it's the default 0.

serialVersionUID: the version label

Without an explicit **serialVersionUID, Java computes one from the class structure, so adding a field makes old saved data fail with InvalidClassException**. Declare it yourself, and compatible changes still load (new fields get defaults).

class Session implements Serializable {
    private static final long
        serialVersionUID = 1L;
}
⚠️ The trap

Compiles, then fails

writeObject accepts any Object, so this compiles — but Point isn't Serializable, so it throws **NotSerializableException** at runtime. The same happens if *any field's* object isn't serializable.

class Point { int x = 1; } // no Serializable
out.writeObject(new Point());
// NotSerializableException

The security problem

Deserialization instantiates classes chosen by the sender and runs their readObject logic. Crafted bytes can chain library classes (gadget chains) into remote code execution. Never deserialize untrusted data — use JSON, or at least an **ObjectInputFilter** allow-list (Java 9).

💼 In the real world

Lessons from incidents

In 2015 researchers showed gadget chains in a common library (Apache Commons Collections) that enabled remote code execution in major Java servers that accepted serialized objects. Since then, teams use JSON or Protobuf for anything crossing a network boundary.

Key takeaways

  1. Implement Serializable, or writeObject throws NotSerializableException
  2. transient fields aren't saved; they come back as defaults
  3. Declare serialVersionUID to control version compatibility
  4. Never deserialize untrusted bytes without an ObjectInputFilter
🤯 Did you know?

Java's chief language architect Brian Goetz has called serialization a "horrible mistake" from 1997, and OpenJDK has published design notes on better replacements.

Practice questions

What does this print?

class User implements Serializable {
    String name = "ana";
    transient String pw = "secret";
}
// copy() = serialize, then deserialize
User u = copy(new User());
System.out.println(u.name + " " + u.pw);
  1. ana secret
  2. ana null
  3. null null
  4. Throws NotSerializableException
Check your answer

ana null. transient fields aren't written. On deserialization the class's constructors and field initializers don't run, so pw is left as null.

What does this print?

class Point { int x = 1; }
// copy() = serialize, then deserialize
Point p = copy(new Point());
System.out.println(p.x);
  1. 1
  2. 0
  3. Throws NotSerializableException
  4. Compile error
Check your answer

Throws NotSerializableException. Point doesn't implement Serializable, so writeObject refuses it at runtime. It compiles because writeObject accepts any Object.

Next: the three pipes every program gets for free — System.in, System.out and System.err.