Singleton is a commonly used design pattern that ensures a class has only one instance and provides a global point of access to it.
However, serialization can silently break the Singleton guarantee by creating a new instance during deserialization. In this article, we'll explore why that happens and how to fix it.
The Problem: Serialization Breaks Singleton
Let's start with a basic Singleton class:
import java.io.*;
class Singleton implements Serializable {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
Now let's serialize and deserialize the singleton:
Singleton instance1 = Singleton.getInstance();
// Serialize to a file
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
out.writeObject(instance1);
out.close();
// Deserialize from the file
ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) in.readObject();
in.close();
System.out.println(instance1 == instance2); // false ❌
Even though the Singleton was correctly implemented, deserialization created a new object, violating the Singleton property.
The Fix: Implement readResolve()
Java provides a hook called readResolve()
that lets you control what object is returned during deserialization.
Fix the Singleton class like this:
class Singleton implements Serializable {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
// This ensures that deserialization returns the original instance
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
Now serialization and deserialization will preserve the singleton instance:
System.out.println(instance1 == instance2); // true ✅
Notes
- The
readResolve()
method must beprivate
and returnObject
. - Without it, Java's serialization mechanism creates a new instance.
- Always test singleton classes that implement
Serializable
.
Bonus: Prevent Reflection-Based Attacks (Optional)
Serialization isn't the only threat to Singleton. Reflection can also break it.
You can throw an exception in the constructor if an instance already exists:
private Singleton() {
if (instance != null) {
throw new RuntimeException("Use getInstance() method to get the single instance of this class");
}
}
Conclusion
Serialization can undermine Singleton, but readResolve()
is a simple yet powerful tool to maintain the Singleton contract.
Always be cautious when marking a Singleton class as Serializable
, especially in distributed systems, caching, or when passing objects over the network.
Top comments (0)