-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Description
Calling ResolvableType#getGenerics() may cause a ResolvableType to become unserializable if the type is generic. Consider the following test
Spring version: 7.0.4 and main, likely 6.x but I didn't check
Java: OpenJDK 17, 21, 25
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.core.ResolvableType;
import static org.assertj.core.api.Assertions.assertThat;
class SerializationTests {
@Test
void serialize() throws Exception {
ResolvableType listType = ResolvableType.forClass(List.class);
testSerialization(listType); // this works
assertThat(listType.getGenerics()).hasSize(1);
testSerialization(listType); // this breaks
}
private static ResolvableType testSerialization(ResolvableType type) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(type);
}
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
ResolvableType read = (ResolvableType) ois.readObject();
assertThat(read).isEqualTo(type);
assertThat(read.getType()).isEqualTo(type.getType());
assertThat(read.resolve()).isEqualTo(type.resolve());
return read;
}
}
}Failing with the following exception
java.io.NotSerializableException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1085)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1451)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1408)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1317)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1079)
at java.base/java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1260)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1075)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1451)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1408)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1317)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1079)
at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:325)
at SerializationTests.testSerialization(SerializationTests.java:26)
at SerializationTests.serialize(SerializationTests.java:20)
The issues is that calling ResolvableType#getGenerics() lazily initializes the generics field with a ResolvedType that references a sun.reflect.generics.reflectiveObjects.TypeVariableImpl which is not serializable. Unfortunately not all implementations of java.lang.reflect.Type are Serializable.
Ideally this would be fixed in OpenJDK but users may not want to wait so long.
I didn't check whether the other lazily computed fields superType and interfaces have the same issue.
An possible fix for this specific issue would be to simply not serialize the lazily computed fields. However doing so in a way that does not break the serialized form can be a bit tricky. Possibly wither implementing writeObject or defining serialPersistentFields.