Serialization and Deserialization
Overview
Serialization converts a Java object graph into a byte stream. Deserialization reconstructs objects from that byte stream. The mechanism is provided by java.io.ObjectOutputStream and java.io.ObjectInputStream.
Serializable Marker Interface
A class must implement java.io.Serializable to be serializable. It declares no methods — it is a marker interface that signals intent to the JVM.
javaimport java.io.Serializable;
public class Product implements Serializable {
private String name;
private double price;
private int stock;
}Exam tip: If an object's class does not implement
Serializable, serializing it throwsNotSerializableException.
serialVersionUID
serialVersionUID is a static final long that the JVM uses to verify that a deserialized class is compatible with the loaded class definition.
javapublic class Product implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private double price;
}If serialVersionUID is not declared, the JVM generates one automatically from the class structure. Changing the class (adding/removing fields) regenerates the UID, causing an InvalidClassException when deserializing old data.
Exam tip: Always declare
serialVersionUIDexplicitly to control versioning. Forgetting it is a common source ofInvalidClassExceptionon exam questions.
Writing and Reading Objects
javaProduct p = new Product("Widget", 9.99);
// Serialize
try (ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("product.ser")))) {
oos.writeObject(p);
}
// Deserialize
try (ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(new FileInputStream("product.ser")))) {
Product loaded = (Product) ois.readObject(); // unchecked cast
System.out.println(loaded.getName());
}readObject() throws ClassNotFoundException (checked) in addition to IOException.
What Gets Serialized
| Element | Serialized? |
|---|---|
| Instance fields (non-transient) | Yes |
transient instance fields |
No — reset to default value on deserialization |
static fields |
No — belong to the class, not the instance |
Superclass fields (if superclass implements Serializable) |
Yes |
Superclass fields (if superclass does NOT implement Serializable) |
No — superclass no-arg constructor is called instead |
transient Fields
Mark fields transient to exclude them from serialization (passwords, caches, open connections):
javapublic class Session implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;
private transient String cachedToken; // not serialized
private transient Connection dbConn; // not serialized
}After deserialization, cachedToken is null and dbConn is null — they must be reinitialised manually.
Inheritance and Serialization
- If a superclass implements
Serializable, all subclasses are automatically serializable. - If a superclass does not implement
Serializable, the superclass's no-arg constructor is invoked during deserialization to initialise the superclass portion. If no accessible no-arg constructor exists, deserialization throwsInvalidClassException.
javapublic class Animal { // NOT Serializable
private String species;
public Animal() { this.species = "Unknown"; } // required
public Animal(String s) { this.species = s; }
}
public class Dog extends Animal implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
}
// On deserialization: Animal() is called, then Dog's fields are restored.Custom Serialization — readObject / writeObject
Override these private methods to control the serialized form:
javapublic class SecureUser implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient char[] password;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // writes non-transient fields
oos.writeInt(password.length);
for (char c : password) oos.writeChar(c);
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // restores non-transient fields
int len = ois.readInt();
password = new char[len];
for (int i = 0; i < len; i++) password[i] = ois.readChar();
}
}defaultWriteObject() and defaultReadObject() must be called first when used.
readResolve and writeReplace
| Method | Purpose |
|---|---|
Object readResolve() |
Returns a replacement object after deserialization (e.g., singletons) |
Object writeReplace() |
Returns a replacement object before serialization |
javapublic class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
private Object readResolve() {
return INSTANCE; // prevents a second instance from being created
}
}Records and Serialization
Records implement Serializable the same way as classes, but their serialization is tighter:
javapublic record Point(int x, int y) implements Serializable {
private static final long serialVersionUID = 1L;
}- Records cannot define
writeObject,readObject,writeReplace, orreadResolve(serialisation is handled differently to preserve the canonical constructor invariant). - The canonical constructor is always invoked during deserialization, guaranteeing validation logic runs.
Exam tip: Custom
readObject/writeObjectmethods are silently ignored for records — the JVM uses the canonical constructor path instead.
Security and Versioning Considerations
- Deserialization of untrusted data is a known attack vector (remote code execution via gadget chains). In production, use
ObjectInputFilteror avoid Java serialization entirely. - Changing
serialVersionUIDdeliberately prevents old streams from being read — use this as a version break when fields change incompatibly. - Adding a field with a matching
serialVersionUIDis generally backward-compatible; removing a field loses that data silently.
Key Points for the Exam
Serializableis a marker interface with no methods.- Declare
serialVersionUIDexplicitly to avoidInvalidClassExceptionsurprises. transientandstaticfields are not serialized.- If the superclass is not
Serializable, its no-arg constructor is called during deserialization. readObject()throws bothIOExceptionandClassNotFoundException.- Records use the canonical constructor during deserialization; custom
readObject/writeObjectare ignored. readResolve()is the standard way to preserve singleton semantics across deserialization.