Serialization
Serialization converts Java objects into byte streams for storage or transmission. Deserialization reconstructs objects from those bytes. This enables object persistence, network transfer, and caching.
Why Serialization?
| Use Case | Description |
|---|---|
| Persistence | Save object state to files or databases |
| Network Transfer | Send objects between JVMs (RMI, distributed systems) |
| Caching | Store objects in memory caches (Redis, Memcached) |
| Deep Copy | Clone objects by serializing and deserializing |
The Serializable Interface
java.io.Serializable is a marker interface (no methods). Implementing
it tells Java that this class can be serialized.
import java.io.Serializable;
public class User implements Serializable {
// Recommended: Define a version ID for compatibility
private static final long serialVersionUID = 1L;
private String username;
private String email;
private int age;
public User(String username, String email, int age) {
this.username = username;
this.email = email;
this.age = age;
}
// Getters and setters...
@Override
public String toString() {
return "User{username='" + username + "', email='" + email + "', age=" + age + "}";
}
}
serialVersionUID: A unique version identifier for the class.
If you modify the class and the UID changes, deserialization of old data will fail
with
InvalidClassException. Always define it explicitly!
Serializing Objects
Use ObjectOutputStream to write objects to a file:
import java.io.*;
public class SerializeDemo {
public static void main(String[] args) {
User user = new User("john_doe", "[email protected]", 25);
// Serialize - save object to file
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.ser"))) {
oos.writeObject(user);
System.out.println("Object serialized successfully!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Deserializing Objects
Use ObjectInputStream to read objects from a file:
import java.io.*;
public class DeserializeDemo {
public static void main(String[] args) {
// Deserialize - read object from file
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.ser"))) {
User user = (User) ois.readObject();
System.out.println("Deserialized: " + user);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
The transient Keyword
Fields marked transient are excluded from serialization. Use this for:
- Sensitive data (passwords, tokens)
- Computed/derived values
- Non-serializable objects
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // NOT serialized
private transient double cachedBalance; // NOT serialized
public Account(String username, String password) {
this.username = username;
this.password = password;
}
}
// After deserialization:
// - username = "john"
// - password = null (transient fields get default values)
// - cachedBalance = 0.0
Serializing Collections
import java.io.*;
import java.util.*;
public class SerializeCollection {
public static void main(String[] args) throws Exception {
List<User> users = new ArrayList<>();
users.add(new User("alice", "[email protected]", 30));
users.add(new User("bob", "[email protected]", 25));
// Serialize the entire list
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("users.ser"))) {
oos.writeObject(users);
}
// Deserialize the list
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("users.ser"))) {
@SuppressWarnings("unchecked")
List<User> loadedUsers = (List<User>) ois.readObject();
loadedUsers.forEach(System.out::println);
}
}
}
Custom Serialization
Override writeObject and readObject for custom behavior:
public class SecureUser implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password;
// Custom serialization - encrypt password before saving
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // Serialize non-transient fields
oos.writeObject(encrypt(password)); // Manually write encrypted password
}
// Custom deserialization - decrypt password after loading
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // Deserialize non-transient fields
this.password = decrypt((String) ois.readObject()); // Decrypt password
}
private String encrypt(String data) { /* encryption logic */ }
private String decrypt(String data) { /* decryption logic */ }
}
Inheritance and Serialization
// Parent class is NOT Serializable
class Person {
String name;
int age;
// MUST have no-arg constructor for deserialization!
public Person() {
this.name = "Unknown";
this.age = 0;
}
}
// Child class IS Serializable
class Employee extends Person implements Serializable {
private static final long serialVersionUID = 1L;
String employeeId;
public Employee(String name, int age, String employeeId) {
this.name = name;
this.age = age;
this.employeeId = employeeId;
}
}
// After deserialization:
// - employeeId = "E123" (serialized)
// - name = "Unknown" (from parent's no-arg constructor)
// - age = 0 (from parent's no-arg constructor)
Rule: If a parent class is not Serializable, it MUST have a
no-argument constructor. Parent fields won't be serialized and will be
initialized via that constructor during deserialization.
Externalizable Interface
For complete control over serialization, implement Externalizable:
import java.io.*;
public class Product implements Externalizable {
private String name;
private double price;
private transient int stockCount;
// REQUIRED: public no-arg constructor
public Product() {}
public Product(String name, double price, int stockCount) {
this.name = name;
this.price = price;
this.stockCount = stockCount;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeDouble(price);
// Don't write stockCount
}
@Override
public void readExternal(ObjectInput in) throws IOException {
name = in.readUTF();
price = in.readDouble();
stockCount = 0; // Default value
}
}
Serialization vs Externalizable
| Feature | Serializable | Externalizable |
|---|---|---|
| Control | Automatic (default behavior) | Full manual control |
| Performance | Slower (uses reflection) | Faster (direct read/write) |
| No-arg constructor | Not required | Required (public) |
| transient fields | Supported | You decide what to write |
Security Considerations
Warning: Java serialization has known security vulnerabilities.
Deserializing untrusted data can lead to remote code execution attacks.
- Never deserialize data from untrusted sources
- Consider using JSON (Jackson, Gson) or Protocol Buffers instead
- Use serialization filters (Java 9+) to restrict allowed classes
Modern Alternatives
// JSON with Jackson (recommended for most use cases)
ObjectMapper mapper = new ObjectMapper();
// Serialize to JSON
String json = mapper.writeValueAsString(user);
// {"username":"john","email":"[email protected]","age":25}
// Deserialize from JSON
User user = mapper.readValue(json, User.class);
// JSON with Gson
Gson gson = new Gson();
String json = gson.toJson(user);
User user = gson.fromJson(json, User.class);
Output
Click Run to execute your code
Summary
- Serializable: Marker interface for automatic serialization
- serialVersionUID: Always define for version compatibility
- transient: Exclude fields from serialization
- ObjectOutputStream/ObjectInputStream: Write/read objects
- Externalizable: Full control over serialization format
- Security: Never deserialize untrusted data; consider JSON alternatives
Enjoying these tutorials?