FULL PRODUCT VERSION :
javac 1.8.0_91
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Mac OS X Darwin Kernel Version 14.5.0: Thu Apr 21 20:40:54 PDT 2016; root:xnu-2782.50.3~1/RELEASE_X86_64 x86_64
A DESCRIPTION OF THE PROBLEM :
ClassCastException is thrown by Java8 upon deserializing a lambda when following conditions are met:
- Parent class has a method, reference to which is used to automatically create a Serializable lambda
- There are several child classes that extend it and there are several usages of above method as a method reference, but with different child classes
- After method reference is consumed it is serialized and the deserialized
- All method references are used within the same capturing class
Please see attached code and actual output for more details.
Also I have made some additional analysis. Upon decompiling this $deserializeLambda$ method with CFR following code is revealed:
private static /* synthetic */ Object $deserializeLambda$(SerializedLambda lambda) {
switch (lambda.getImplMethodName()) {
case "convert": {
if (lambda.getImplMethodKind() == 5 && lambda.getFunctionalInterfaceClass().equals("LambdaSerializationTest$MyFunction") && lambda.getFunctionalInterfaceMethodName().equals("call") && lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") && lambda.getImplClass().equals("LambdaSerializationTest$AbstractConverter") && lambda.getImplMethodSignature().equals("(Ljava/lang/String;)Ljava/lang/String;")) {
return (MyFunction<String, String>)LambdaMetafactory.altMetafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;, convert(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)((ConverterA)((ConverterA)lambda.getCapturedArg(0)));
}
if (lambda.getImplMethodKind() == 5 && lambda.getFunctionalInterfaceClass().equals("LambdaSerializationTest$MyFunction") && lambda.getFunctionalInterfaceMethodName().equals("call") && lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") && lambda.getImplClass().equals("LambdaSerializationTest$AbstractConverter") && lambda.getImplMethodSignature().equals("(Ljava/lang/String;)Ljava/lang/String;")) {
return (MyFunction<String, String>)LambdaMetafactory.altMetafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;, convert(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)((ConverterB)((ConverterB)lambda.getCapturedArg(0)));
}
if (lambda.getImplMethodKind() != 5 || !lambda.getFunctionalInterfaceClass().equals("LambdaSerializationTest$MyFunction") || !lambda.getFunctionalInterfaceMethodName().equals("call") || !lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") || !lambda.getImplClass().equals("LambdaSerializationTest$AbstractConverter") || !lambda.getImplMethodSignature().equals("(Ljava/lang/String;)Ljava/lang/String;")) break;
return (MyFunction<String, String>)LambdaMetafactory.altMetafactory(null, null, null, (Ljava/lang/Object;)Ljava/lang/Object;, convert(java.lang.String ), (Ljava/lang/String;)Ljava/lang/String;)((ConverterC)((ConverterC)lambda.getCapturedArg(0)));
}
}
throw new IllegalArgumentException("Invalid lambda deserialization");
}
We observe that LambdaSerializationTest$AbstractConverter is used as implClass instead of concrete LambdaSerializationTest$ConverterX. So all 3 lambdas will satisfy 1st if condition and ConverterA is always assumed.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Please run the attached test case and observe the output.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Output:
1.8.0_91
test_A
test_B
test_C
ACTUAL -
Output:
1.8.0_91
test_A
Exception in thread "main" java.lang.RuntimeException: java.io.IOException: unexpected exception type
at LambdaSerializationTest.serializeDeserialize(LambdaSerializationTest.java:68)
at LambdaSerializationTest.giveFunction(LambdaSerializationTest.java:52)
at LambdaSerializationTest.main(LambdaSerializationTest.java:47)
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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.io.IOException: unexpected exception type
at java.io.ObjectStreamClass.throwMiscException(ObjectStreamClass.java:1582)
at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:1154)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1817)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at LambdaSerializationTest.serializeDeserialize(LambdaSerializationTest.java:65)
... 7 more
Caused by: java.lang.reflect.InvocationTargetException
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.lang.invoke.SerializedLambda.readResolve(SerializedLambda.java:230)
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.invokeReadResolve(ObjectStreamClass.java:1148)
... 11 more
Caused by: java.lang.ClassCastException: LambdaSerializationTest$ConverterB cannot be cast to LambdaSerializationTest$ConverterA
at LambdaSerializationTest.$deserializeLambda$(LambdaSerializationTest.java:7)
... 21 more
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.lang.RuntimeException: java.io.IOException: unexpected exception type
at LambdaSerializationTest.serializeDeserialize(LambdaSerializationTest.java:68)
at LambdaSerializationTest.giveFunction(LambdaSerializationTest.java:52)
at LambdaSerializationTest.main(LambdaSerializationTest.java:47)
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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.io.IOException: unexpected exception type
at java.io.ObjectStreamClass.throwMiscException(ObjectStreamClass.java:1582)
at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:1154)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1817)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at LambdaSerializationTest.serializeDeserialize(LambdaSerializationTest.java:65)
... 7 more
Caused by: java.lang.reflect.InvocationTargetException
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.lang.invoke.SerializedLambda.readResolve(SerializedLambda.java:230)
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.invokeReadResolve(ObjectStreamClass.java:1148)
... 11 more
Caused by: java.lang.ClassCastException: LambdaSerializationTest$ConverterB cannot be cast to LambdaSerializationTest$ConverterA
at LambdaSerializationTest.$deserializeLambda$(LambdaSerializationTest.java:7)
... 21 more
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.*;
/**
* @author Max Myslyvtsev
* @since 7/6/16
*/
public class LambdaSerializationTest implements Serializable {
static abstract class AbstractConverter implements Serializable {
String convert(String input) {
return doConvert(input);
}
abstract String doConvert(String input);
}
static class ConverterA extends AbstractConverter {
@Override
String doConvert(String input) {
return input + "_A";
}
}
static class ConverterB extends AbstractConverter {
@Override
String doConvert(String input) {
return input + "_B";
}
}
static class ConverterC extends AbstractConverter {
@Override
String doConvert(String input) {
return input + "_C";
}
}
interface MyFunction<T, R> extends Serializable {
R call(T var);
}
public static void main(String[] args) throws Exception {
System.out.println(System.getProperty("java.version"));
ConverterA converterA = new ConverterA();
ConverterB converterB = new ConverterB();
ConverterC converterC = new ConverterC();
giveFunction(converterA::convert);
giveFunction(converterB::convert);
giveFunction(converterC::convert);
}
private static void giveFunction(MyFunction<String, String> f) {
f = serializeDeserialize(f);
System.out.println(f.call("test"));
}
private static <T> T serializeDeserialize(T object) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
@SuppressWarnings("unchecked")
T result = (T) ois.readObject();
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
There are following workarounds available:
- Do not use such method references in same capturing class, but rather spread it by different (possibly inner) capturing classes.
- Do not use method references as actual arguments, but use inner classes instead.