JDK-8249598 : (de)serialization circular references with HashSets fail in .hashCode()
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io:serialization
  • Affected Version: 8,11.0.2,15
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic
  • CPU: x86_64
  • Submitted: 2020-07-09
  • Updated: 2024-06-12
  • Resolved: 2024-06-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdResolved
Related Reports
Duplicate :  
Description
ADDITIONAL SYSTEM INFORMATION :
Same issue with Java 1.8.0_151 and 11.0.1+13.  I'm running the test from IntelliJ:

IntelliJ IDEA 2019.1 (Ultimate Edition)
Build #IU-191.6183.87, built on March 27, 2019
JRE: 1.8.0_202-release-1483-b39 amd64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Linux 4.15.0-47-generic

A DESCRIPTION OF THE PROBLEM :
```java
public static class TestClass implements Serializable {

    String name;
    Set<TestClass> others = new HashSet();

    TestClass(String n) { name = n; }


    @Override
    public int hashCode() {
        System.out.println("name=" + name);
        return name.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        return (other instanceof TestClass) &&
               Objects.equals(this.name, ((TestClass) other).name);
    }

    private static final long serialVersionUID = 20141029195011L;
}

@Test
public void testDeserialization() {
    TestClass alice = new TestClass("Alice");
    TestClass bob = new TestClass("Bob");
    alice.others.add(bob);
    bob.others.add(alice);

    assertEquals(alice, serializeDeserialize(alice));
}
```
Code for serializeDeserialize is pasted into code box below.

Output
```
name=Bob
name=Alice
name=null

java.lang.NullPointerException
	at MyTest$TestClass.hashCode(ComparatorContractTest.java:69)
	at java.util.HashMap.hash(HashMap.java:339)
	at java.util.HashMap.put(HashMap.java:612)
	at java.util.HashSet.readObject(HashSet.java:342)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2178)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at java.util.HashSet.readObject(HashSet.java:341)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2178)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at org.organicdesign.testUtils.Serialization.serializeDeserialize(Serialization.java:38)
	at MyTest.testDeserialization(ComparatorContractTest.java:88)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
```

It looks like both objects are deserialized correctly (because they are printed out), but then something tries to deserialize one object a third time and it gets initialized with nulls (or is not initialized).

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Cut and paste the test in the description into a Java file and run it.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The test should pass that the objects match before and after serialization.
ACTUAL -
The objects are not deserialized due to the circular reference and possibly trying to read more objects than were serialized.

---------- BEGIN SOURCE ----------
public static class TestClass implements Serializable {

    String name;
    Set<TestClass> others = new HashSet<>();

    TestClass(String n) { name = n; }


    @Override
    public int hashCode() {
        System.out.println("name=" + name);
        return name.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        return (other instanceof TestClass) &&
               Objects.equals(this.name, ((TestClass) other).name);
    }

    private static final long serialVersionUID = 20141029195011L;
}

@Test
public void testDeserialization() {
    TestClass alice = new TestClass("Alice");
    TestClass bob = new TestClass("Bob");
    alice.others.add(bob);
    bob.others.add(alice);

    assertEquals(alice, serializeDeserialize(alice));
}

public static <T> T serializeDeserialize(T obj) {

    // This method was started by sblommers.  Thanks for your help!
    // Mistakes are Glen's.
    // https://github.com/GlenKPeterson/Paguro/issues/10#issuecomment-242332099

    // Write
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    try {
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);

        final byte[] data = baos.toByteArray();

        // Read
        ByteArrayInputStream baip = new ByteArrayInputStream(data);
        ObjectInputStream ois = new ObjectInputStream(baip);
        return (T) ois.readObject();
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}

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

CUSTOMER SUBMITTED WORKAROUND :
I guess you could write your own serialization/deserialization or add a serialization proxy, or your own collection, or your own serialization library.  Not really eager to do any of those.

FREQUENCY : always



Comments
Re-closing as duplicate of JDK-6208166. My previous comment referring to JDK-8201131 was misleading, as that bug relates to a more complicated case involving subclassing.
12-06-2024

Closing it out as a duplicate of JDK-8201131.
12-06-2024

Related to, if not a duplicate of, JDK-8201131 and JDK-8199664.
16-07-2020

After serializing circular references of a HashMap, deserializing causes the reference to be lost in hashcode method.
16-07-2020