Explain Codes LogoExplain Codes Logo

Java serialization: readObject() vs. readResolve()

java
singleton
serialization
deserialization
Anton ShumikhinbyAnton Shumikhin·Feb 27, 2025
TLDR

The readObject() method customizes deserialization by restoring an object's state from an input stream:

private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { // Wake up, Neo... The Matrix has you... }

Meanwhile, readResolve() manipulates post-deserialization outcomes like preserving singletons by switching the deserialized object with another:

private Object readResolve() throws ObjectStreamException { // There can be only one (Highlander style) return INSTANCE; }

Simply put, readObject() dictates the how of deserialization, readResolve() controls the what after deserialization.

Usage scenarios for readResolve

The readResolve() can be useful when you must meet post-deserialization requirements like enforcing singleton pattern or maintaining immutable properties. When dealing with a singleton class, a new instance gets created during deserialization. Implementing readResolve() can help replace the deserialized object with the singleton instance:

public class Singleton implements Serializable { public static final Singleton INSTANCE = new Singleton(); protected Object readResolve() { // Keeping it real Singleton return INSTANCE; } }

For an immutable property, readResolve() can be used to maintain its state during deserialization, even when they have final fields.

Working with readObject and readResolve

  • Duplicate elimination: Use readResolve() to compare a deserialized object with a pre-existing instance registry to replace duplicates.
  • Consistency assurance: In distributed systems, readResolve() can update object references for synchronized versions across multiple nodes.
  • Custom object initialization: Fields not serialized can be initialized consistently by using APIs like xstream and readResolve().

Serialization proxies explained

As introduced in Effective Java, the serialization proxy pattern uses writeReplace to serialize a separate object that encapsulates the logical state. During deserialization, readResolve() converts this proxy back to the original object thus providing a safer technique for the serialization process.

private Object writeReplace() { // Creating my alter ego return new SerializationProxy(this); } private static class SerializationProxy implements Serializable { private final int data; SerializationProxy(YourClass yc) { this.data = yc.data; } private Object readResolve() { // Back to being myself return new YourClass(this.data); } }

Dive into readResolve

  • Type variety: The return type of readResolve() can be of the same type or even a different class, depending on what you want to substitute the deserialized object with.
  • Execution timeline: readResolve() is called before deserialization, letting you replace the deserialized object.

Common pitfalls

  • Misuse of readResolve: Unnecessary use of readResolve() might complicate your code.
  • Overriding variables: Beware of readResolve() unintentionally overriding final variables.
  • Security issues: Be mindful about potentially harmful payloads exploiting the readObject() or readResolve() methods.