Java serialization: readObject() vs. readResolve()

Viewed 100713

The book Effective Java and other sources provide a pretty good explanation on how and when to use the readObject() method when working with serializable Java classes. The readResolve() method, on the other hand, remains a bit of a mystery. Basically all documents I found either mention only one of the two or mention both only individually.

Questions that remain unanswered are:

  • What is the difference between the two methods?
  • When should which method be implemented?
  • How should readResolve() be used, especially in terms of returning what?

I hope you can shed some light on this matter.

10 Answers

readResolve is used for replacing the object read from the stream. The only use I've ever seen for this is enforcing singletons; when an object is read, replace it with the singleton instance. This ensures that nobody can create another instance by serializing and deserializing the singleton.

Item 90, Effective Java, 3rd Ed covers readResolve and writeReplace for serial proxies - their main use. The examples do not write out readObject and writeObject methods because they are using default serialisation to read and write fields.

readResolve is called after readObject has returned (conversely writeReplace is called before writeObject and probably on a different object). The object the method returns replaces this object returned to the user of ObjectInputStream.readObject and any further back references to the object in the stream. Both readResolve and writeReplace may return objects of the same or different types. Returning the same type is useful in some cases where fields must be final and either backward compatibility is required or values must copied and/or validated.

Use of readResolve does not enforce the singleton property.

As already answered, readResolve is an private method used in ObjectInputStream while deserializing an object. This is called just before actual instance is returned. In case of Singleton, here we can force return already existing singleton instance reference instead of deserialized instance reference. Similary we have writeReplace for ObjectOutputStream.

Example for readResolve:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SingletonWithSerializable implements Serializable {
private static final long serialVersionUID = 1L;

public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable();

private SingletonWithSerializable() {
    if (INSTANCE != null)
        throw new RuntimeException("Singleton instance already exists!");
}

private Object readResolve() {
    return INSTANCE;
}

public void leaveTheBuilding() {
    System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called...");
}

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE;

    System.out.println("Before serialization: " + instance);

    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) {
        out.writeObject(instance);
    }

    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) {
        SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject();
        System.out.println("After deserialization: " + readObject);
    }

}

}

Output:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922

I know this question is really old and has an accepted answer, but as it pops up very high in google search I thought I'd weigh in because no provided answer covers the three cases I consider important - in my mind the primary use for these methods. Of course, all assume that there is actually a need for custom serialization format.

Take, for example collection classes. Default serialization of a linked list or a BST would result in a huge loss of space with very little performance gain comparing to just serializing the elements in order. This is even more true if a collection is a projection or a view - keeps a reference to a larger structure than it exposes by its public API.

  1. If the serialized object has immutable fields which need custom serialization, original solution of writeObject/readObject is insufficient, as the deserialized object is created before reading the part of the stream written in writeObject. Take this minimal implementation of a linked list:

    public class List<E> extends Serializable {
        public final E head;
        public final List<E> tail;
    
        public List(E head, List<E> tail) {
            if (head==null)
                throw new IllegalArgumentException("null as a list element");
            this.head = head;
            this.tail = tail;
        }
    
        //methods follow...
    }
    

This structure can be serialized by recursively writing the head field of every link, followed by a null value. Deserializing such a format becomes however impossible: readObject can't change the values of member fields (now fixed to null). Here come the writeReplace/readResolve pair:

private Object writeReplace() {
    return new Serializable() {
        private transient List<E> contents = List.this;

        private void writeObject(ObjectOutputStream oos) {
            List<E> list = contents;
            while (list!=null) {
                oos.writeObject(list.head);
                list = list.tail;
            }
            oos.writeObject(null);
        }

        private void readObject(ObjectInputStream ois) {
            List<E> tail = null;
            E head = ois.readObject();
            if (head!=null) {
                readObject(ois); //read the tail and assign it to this.contents
                this.contents = new List<>(head, this.contents)
            }                     
        }


        private Object readResolve() {
            return this.contents;
        }
    }
}

I am sorry if the above example doesn't compile (or work), but hopefully it is sufficient to illustrate my point. If you think this is a very far fetched example please remember that many functional languages run on the JVM and this approach becomes essential in their case.

  1. We may want to actually deserialize an object of a different class than we wrote to the ObjectOutputStream. This would be the case with views such as a java.util.List list implementation which exposes a slice from a longer ArrayList. Obviously, serializing the whole backing list is a bad idea and we should only write the elements from the viewed slice. Why stop at it however and have a useless level of indirection after deserialization? We could simply read the elements from the stream into an ArrayList and return it directly instead of wrapping it in our view class.

  2. Alternatively, having a similar delegate class dedicated to serialization may be a design choice. A good example would be reusing our serialization code. For example, if we have a builder class (similar to the StringBuilder for String), we can write a serialization delegate which serializes any collection by writing an empty builder to the stream, followed by collection size and elements returned by the colection's iterator. Deserialization would involve reading the builder, appending all subsequently read elements, and returning the result of final build() from the delegates readResolve. In that case we would need to implement the serialization only in the root class of the collection hierarchy, and no additional code would be needed from current or future implementations, provided they implement abstract iterator() and builder() method (the latter for recreating the collection of the same type - which would be a very useful feature in itself). Another example would be having a class hierarchy which code we don't fully control - our base class(es) from a third party library could have any number of private fields we know nothing about and which may change from one version to another, breaking our serialized objects. In that case it would be safer to write the data and rebuild the object manually on deserialization.

Related