Serialization in Java
Serialization converts an object into a byte stream so it can be saved to a file, sent over a network, or stored in a cache. Deserialization is the reverse — converting bytes back into an object.
How It Works
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
subgraph Serialization
direction LR
A(("Java Object")) -->|serialize| B[/"Byte Stream"/]
B --> C(["File / DB / Network"])
end
subgraph Deserialization
direction LR
D(["File / DB / Network"]) -->|deserialize| E[/"Byte Stream"/]
E --> F(("Java Object<br/><i>restored</i>"))
end
style A fill:#D1FAE5,color:#1E40AF
style B fill:#FEF3C7,color:#1E40AF
style C fill:#BFDBFE,color:#1E40AF
style D fill:#BFDBFE,color:#1E40AF
style E fill:#FEF3C7,color:#1E40AF
style F fill:#D1FAE5,color:#1E40AF Basic Example
Step 1: Implement Serializable
import java.io.Serializable;
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return name + " | Age: " + age + " | Salary: " + salary;
}
}
Step 2: Serialize (Write object to file)
Employee emp = new Employee("Vamsi", 27, 150000);
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("employee.ser"))) {
oos.writeObject(emp);
System.out.println("Serialized: " + emp);
}
Step 3: Deserialize (Read object from file)
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("employee.ser"))) {
Employee emp = (Employee) ois.readObject();
System.out.println("Deserialized: " + emp);
}
serialVersionUID — Why It Matters
serialVersionUID is a version number for your class. If you serialize an object and then change the class (add/remove fields), the serialVersionUID changes and deserialization fails with InvalidClassException.
| Scenario | What happens |
|---|---|
No serialVersionUID declared | JVM auto-generates one — changes if class changes |
Explicit serialVersionUID + add new field | Deserialization works — new field gets default value |
Explicit serialVersionUID + remove field | Deserialization works — removed field is ignored |
Different serialVersionUID | InvalidClassException — deserialization fails |
transient Keyword — Excluding Fields
Fields marked transient are not serialized. Use it for sensitive data or derived fields.
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // NOT serialized
private transient int loginCount; // NOT serialized
public User(String username, String password) {
this.username = username;
this.password = password;
this.loginCount = 0;
}
}
User user = new User("vamsi", "secret123");
// Serialize
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.ser"))) {
oos.writeObject(user);
}
// Deserialize
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.ser"))) {
User restored = (User) ois.readObject();
System.out.println(restored.username); // "vamsi"
System.out.println(restored.password); // null (transient!)
System.out.println(restored.loginCount); // 0 (transient!)
}
static Fields and Serialization
static fields belong to the class, not the object. They are never serialized.
public class Config implements Serializable {
private static String appName = "MyApp"; // NOT serialized
private String userId; // serialized
}
Serializable vs Externalizable
| Feature | Serializable | Externalizable |
|---|---|---|
| Marker interface | Yes (no methods) | No (has 2 methods) |
| Control | JVM handles everything | You control read/write |
| Performance | Slower (serializes all fields) | Faster (you choose what to write) |
| Default constructor | Not required | Required (public no-arg) |
transient | Respected | You decide manually |
Externalizable Example
public class Product implements Externalizable {
private String name;
private double price;
private transient String internalCode;
public Product() {} // REQUIRED for Externalizable
public Product(String name, double price, String code) {
this.name = name;
this.price = price;
this.internalCode = code;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeDouble(price);
// deliberately NOT writing internalCode
}
@Override
public void readExternal(ObjectInput in) throws IOException {
this.name = in.readUTF();
this.price = in.readDouble();
}
}
Serialization with Inheritance
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
Parent{{"Parent<br/><i>NOT Serializable</i><br/>Fields get DEFAULT values"}}
Child(["Child<br/><i>Serializable</i><br/>Fields ARE serialized"])
Parent -->|extends| Child
style Parent fill:#FEE2E2,color:#1E40AF,stroke:#FCA5A5,stroke-width:2px
style Child fill:#D1FAE5,color:#1E40AF,stroke:#6EE7B7,stroke-width:2px class Animal { // NOT Serializable
String species;
Animal() { this.species = "Unknown"; }
Animal(String species) { this.species = species; }
}
class Dog extends Animal implements Serializable {
private static final long serialVersionUID = 1L;
String name;
Dog(String species, String name) {
super(species);
this.name = name;
}
}
// After serialization + deserialization:
Dog dog = new Dog("Canine", "Buddy");
// dog.name → "Buddy" (serialized — child field)
// dog.species → "Unknown" (NOT serialized — parent's no-arg constructor runs)
Custom Serialization with readObject / writeObject
You can customize the default serialization behavior:
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
private String accountNumber;
private transient String encryptedPassword;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeObject(encrypt(encryptedPassword)); // custom write
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
this.encryptedPassword = decrypt((String) ois.readObject()); // custom read
}
}
Modern Alternatives to Java Serialization
Java serialization has security vulnerabilities and is considered legacy. Modern alternatives:
| Format | Library | When to use |
|---|---|---|
| JSON | Jackson, Gson | REST APIs, config files |
| Protocol Buffers | Google protobuf | High-performance, cross-language RPC |
| Avro | Apache Avro | Kafka messages, schema evolution |
| Kryo | Kryo | In-memory serialization, fast |
| MessagePack | msgpack-java | Compact binary, cross-language |
Interview Questions
1. What happens if a Serializable class contains a non-Serializable field?
NotSerializableException at runtime. To fix: either make the field's class Serializable, mark the field transient, or use custom serialization (writeObject/readObject).
2. How can you prevent a Serializable class from being serialized?
Throw an exception in writeObject: private void writeObject(ObjectOutputStream oos) throws IOException { throw new NotSerializableException("Serialization not allowed"); }. Or implement readResolve() for Singleton protection.
3. How does Singleton break with serialization, and how do you fix it?
Deserializing creates a new object, breaking the Singleton contract. Fix: implement readResolve() to return the existing instance.
4. What is the difference between transient and static in the context of serialization?
Both are excluded from serialization, but for different reasons. transient — explicitly marks an instance field to skip. static — belongs to the class, not the object, so it's inherently not part of object state. After deserialization, transient fields get default values (null, 0), while static fields retain whatever value the class currently has.