Serialization in Java
Introduction
Serialization is the process of converting an object's state into a byte stream. This byte stream can then be saved to a file, stored in a database, or transmitted over a network. The reverse process—converting a byte stream back into a copy of the object—is called Deserialization.
In Java, serialization is deeply integrated into the standard library and is commonly used in technologies like RMI (Remote Method Invocation), JPA/Hibernate (for caching), and enterprise application servers.
How Serialization Works in Java
To make a Java object serializable, its class must implement the java.io.Serializable interface. This is a marker interface—it has no methods or fields and serves only to tell the JVM that instances of the class can be serialized.
Basic Implementation
import java.io.*;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // Will not be serialized
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() { return username; }
public String getPassword() { return password; }
}
Serializing an Object
We use ObjectOutputStream wrapped around a FileOutputStream to write the object to a file.
public class SerializationDemo {
public static void main(String[] args) {
User user = new User("admin", "secret123");
try (FileOutputStream fileOut = new FileOutputStream("user.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(user);
System.out.println("Serialized data is saved in user.ser");
} catch (IOException i) {
i.printStackTrace();
}
}
}
Deserializing an Object
To read the object back, we use ObjectInputStream.
public class DeserializationDemo {
public static void main(String[] args) {
User user = null;
try (FileInputStream fileIn = new FileInputStream("user.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {
user = (User) in.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("Deserialized User: " + user.getUsername());
System.out.println("Password: " + user.getPassword()); // Outputs 'null' due to transient
}
}
Advanced Concepts
1. The transient Keyword
When a field is marked as transient, it is ignored during the serialization process. Upon deserialization, the field is initialized with its default value (e.g., null for objects, 0 for integers). This is crucial for sensitive data like passwords or fields that don't make sense to serialize (like thread instances or database connections).
2. serialVersionUID
The serialization runtime associates a version number with each serializable class, called serialVersionUID. It's used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible.
If you don't explicitly declare a serialVersionUID, the JVM generates one based on various aspects of the class. Modifying the class (e.g., adding a field) changes this hash, resulting in an InvalidClassException when trying to deserialize old objects.
3. Custom Serialization (writeObject and readObject)
You can take control of the serialization process by providing private methods with exact signatures:
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// custom logic, e.g., encrypting a field
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// custom logic, e.g., decrypting a field
}
4. Externalizable Interface
For total control, implement Externalizable instead of Serializable. It requires you to implement writeExternal(ObjectOutput out) and readExternal(ObjectInput in). Unlike Serializable, it uses the public no-arg constructor to instantiate the object before reading fields.
Best Practices and Caveats
- Security: Java's native serialization has a notoriously bad security track record (vulnerable to gadget chain attacks). Never deserialize untrusted data.
- Performance: Standard serialization is slow and produces large payloads.
- Alternatives: In modern applications, prefer data formats like JSON (via Jackson/Gson) or Protocol Buffers. They are cross-language compatible, safer, and much faster.