Serialization — Serializable, transient, ObjectOutputStream, readObject, and Versioning
In this tutorial, you will learn about Serialization. We cover key concepts, practical examples, and best practices to help you master this topic.
Java serialization converts objects into a byte stream for persistence or transmission, using the Serializable marker interface. Serialization is the JVM's built-in mechanism for object persistence — it flattens a graph of connected objects into a sequence of bytes that can be written to a file, sent over a network, or stored in a database.
What You'll Learn
- Implementing Serializable
- The serialization mechanism: ObjectOutputStream/ObjectInputStream
- Transient fields and custom serialization
- Versioning with serialVersionUID
Why It Matters
Serialization is the foundation of Java RMI, session replication in application servers, and many caching solutions. Understanding serialization prevents security vulnerabilities (deserialization attacks) and compatibility issues when classes evolve.
Real-World Use
HTTP sessions serialize user data for clustering. Spark distributes data across cluster nodes via serialization. RMI and EJB use serialization for remote method calls.
Basic Serialization
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private String email;
// constructor, getters, setters
}
Serializing:
Person person = new Person("Alice", 30, "alice@example.com");
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
oos.writeObject(person);
}
Deserializing:
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
System.out.println(person.getName());
}
The serialVersionUID
Every serializable class has a version identifier:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
// ...
}
If you do not declare one, the JVM computes it from the class structure. This computed UID changes when you add or remove fields, causing InvalidClassException on deserialization.
Explicit UID Benefits
- Backward compatibility — old serialized data can be read after adding fields
- Deterministic — not dependent on compiler implementation
- Performance — avoids computing at runtime
Transient Fields
Fields marked transient are skipped during serialization:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // not serialized
private transient Logger logger = Logger.getLogger(); // not serializable
}
When deserialized, transient fields receive their default values (null for objects, 0 for primitives).
Custom Serialization
Implement writeObject and readObject to customize the Process:
public class SecureData implements Serializable {
private static final long serialVersionUID = 1L;
private String data;
private transient String encrypted;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeObject(encrypt(data)); // custom serialization
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
this.encrypted = (String) ois.readObject();
this.data = decrypt(this.encrypted);
}
private String encrypt(String s) { /* ... */ }
private String decrypt(String s) { /* ... */ }
}
readResolve — Singleton Protection
Prevents deserialization from creating a new instance:
public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
// Ensure single instance after deserialization
private Object readResolve() {
return INSTANCE;
}
}
Inheritance and Serialization
- If a parent class implements
Serializable, all subclasses are serializable - If a parent class does NOT implement
Serializable, its fields are NOT serialized
public class Animal {
protected String species; // NOT serialized
}
public class Dog extends Animal implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
}
// When deserializing Dog:
// - name is restored from stream
// - species is NOT restored (the no-arg constructor of Animal is called)
Deserialization Security
Deserializing untrusted data is dangerous — it can trigger arbitrary code execution:
// Always validate what you deserialize
try (ObjectInputStream ois = new ObjectInputStream(inputStream)) {
Object obj = ois.readObject();
if (!(obj instanceof Person)) {
throw new SecurityException("Unexpected type: " + obj.getClass());
}
Person person = (Person) obj;
}
Safe Deserialization Practices
- Use a whitelist of allowed classes
- Validate the object after deserialization
- Prefer JSON/XML over Java serialization for external data
- Consider using
SerializationFilter(Java 9+):ObjectInputFilter.Config.setSerialFilter(filter)
Common Mistakes
- Not declaring serialVersionUID. Adding any field to a class without updating the UID breaks deserialization of old data.
- Serializing inner classes. Non-static inner classes have an implicit reference to the enclosing instance, which can cause unintentional serialization of large object graphs.
- Forgetting transient for non-serializable fields. If a field references a non-serializable object and is not transient, serialization throws
NotSerializableException. - Relying on default serialization for sensitive data. Transient passwords are not serialized, but all other fields are — including credit card numbers.
- Deserializing untrusted data. Deserialization attacks can execute arbitrary code. Never deserialize from untrusted sources.
Practice Questions
1. What is the purpose of serialVersionUID?
It identifies the version of a serializable class. During deserialization, the JVM compares the UID of the streamed class with the local class — mismatches cause InvalidClassException.
2. What does the transient keyword do?
It marks a field as non-serializable. During serialization, transient fields are skipped and restored to their default values.
3. How does readResolve() work?
After deserialization, if readResolve() is defined, the returned object replaces the deserialized instance. Used for singleton and enum patterns.
4. Why is Java serialization considered unsafe?
Malformed serialized data can trigger arbitrary code execution during deserialization. Libraries like ObjectInputStream can instantiate arbitrary classes and call methods on them.
5. What happens when a parent class does not implement Serializable?
The parent's fields are not serialized. During deserialization, the parent's no-arg constructor is called to initialize non-serialized fields.
Challenge Question:
Create a Configuration class that stores settings as a Map<String, String>. It should be serializable. Add custom serialization that encrypts sensitive keys (anything containing "password" or "secret") before serialization and decrypts after deserialization. Use transient for the logger. Write a unit test that serializes, deserializes, and verifies the configuration.
FAQ
Mini Project
Write a program SerializationDemo.java that:
- Creates an
Employeeclass implementingSerializablewith fieldsid,name,salary,password(transient) - Serializes an
Employeeto a file usingObjectOutputStream - Deserializes it back and shows that password is null
- Adds custom
writeObject/readObjectthat compresses salary data (store as int cents instead of double) - Implements
readResolvefor aCompanysingleton - Attempts to deserialize a corrupted/modified file and handles
InvalidClassException - Demonstrates that adding a field without updating serialVersionUID breaks deserialization
What's Next
Serialization stores objects, but many applications need key-value configuration. Lesson 44 covers Properties and Configuration — the Properties class, ResourceBundle for Internationalization, configuration files, and the Preferences API.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro