JDK-6842012 : "writeReplace" and "readResolve" problem with cyclic objet graph
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io:serialization
  • Affected Version: 6u13
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2009-05-17
  • Updated: 2011-02-16
  • Resolved: 2009-05-18
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_13"
Java(TM) SE Runtime Environment (build 1.6.0_13-b03)
Java HotSpot(TM) Client VM (build 11.3-b02, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
We have 2 objects that reference each other (instances of class 'CA' and 'CB')
CA and CB are serializable
'a' is an instance of CA, 'b' is an instance of CB
'a' has a reference on 'b' (a.refB = b)
'b' has a reference on 'a' (b.refA = a)

In our case, we cant serialize instances of CA (they are in fact dynamically generated classes)
So we implement "writeReplace" in class CA to replace instances of class CA by instances of CC in the serialization process.
And in class CC we add "readResolve" to replace back instances of CC by instances of CA in the de-serialization process.

With an object graph like this :
   a -> b ('a' has a reference on 'b')
'a'.writeReplace and 'c'.readResolve are called (we can check this with a debugger), and serialization and deserialization work properly.

But with an object graph like this :
  a <-> b ('a' has a reference on 'b' and 'b' has a reference on 'a')
'a'.writeReplace is called (this has been verified with a debugger), but this time 'c'.readResolve is never called and a ClassCastException is thrown while deserialization (ObjectInputStream tries to do "b.refA = c", instead of "b.refA = a")

The source code in this repport show this problem.



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Launch the code bellow. A ClassCastException is thrown each time

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
no ClassCastException should be thrown
ACTUAL -
java.lang.ClassCastException: cannot assign instance of com.tentelemed.serialtest.Main$DataTransfer to field com.tentelemed.serialtest.Main$Center.user of type com.tentelemed.serialtest.Main$User in instance of com.tentelemed.serialtest.Main$Center
	at java.io.ObjectStreamClass$FieldReflector.setObjFieldValues(ObjectStreamClass.java:2032)
	at java.io.ObjectStreamClass.setObjFieldValues(ObjectStreamClass.java:1212)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1953)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1871)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
	at java.util.HashMap.readObject(HashMap.java:1030)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1849)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1947)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1871)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
	at com.tentelemed.serialtest.Main.serialize(Main.java:27)
	at com.tentelemed.serialtest.Main.start(Main.java:72)
	at com.tentelemed.serialtest.Main.main(Main.java:16)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.util.Map;
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        Main m = new Main();
        m.start();
    }

    static class Center implements Serializable {
        public User user;
        public String name;
    }

    static class User implements Serializable {
        public Center center;
        public String name;
        public Object writeReplace() {
            DataTransfer dt = new DataTransfer();
            dt.params.put("name", name);
            dt.params.put("center", center);
            return dt;
        }
    }

    static class DataTransfer implements Serializable {
        public Map<String, Object> params = new HashMap<String, Object>();
        public Object readResolve() {
            User user = new User();
            String name = (String) params.get("name");
            Center center = (Center) params.get("center");
            user.name = name;
            user.center = center;
            return user;
        }
    }

    public static <I> I serialize(I o) {
        try {
            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(buf);
            out.writeObject(o);
            out.flush();
            out.close();
            ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));
            Object dest = in.readObject();
            in.close();
            return (I) dest;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void start() {
        Center center = new Center();
        center.name = "centre1";
        User user = new User();
        user.name = "Dupont";
        user.center = center;
        center.user = user;

        User user1 = serialize(user);
        System.err.println("result : "+user1);
    }
}

---------- END SOURCE ----------

Comments
EVALUATION This is essentially the same issue as is described in 6785441-- please see the Evaluation for that bug report.
18-05-2009