JDK-8161257 : Java8 Lambda Deserialization - ClassCastException
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 8,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 2016-07-07
  • Updated: 2017-02-02
  • Resolved: 2017-02-02
Related Reports
Duplicate :  
Description
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.




Comments
This is a duplicate of JDK-8154236. javac is generating a synthetic method to derserialize all lambdas associated with BSMs in the class. Unfortunately that method does not distinguish between sub type instances that share the same BSM and therefore the super type associated with the concrete method bound to the functional interface.
02-02-2017

Eclipse compiles and executes properly whereas javac throws RuntimeException
11-07-2016

This issue verified on 8u91 and 9 ea b125 and found to be issue 8u92 - Fail 9 ea b-125 - Fail -sh-4.1$ /opt/java/jdk-9_ea-125/bin/javac LambdaSerializationTest.java -sh-4.1$ /opt/java/jdk-9_ea-125/bin/java LambdaSerializationTest 9-ea test_A Exception in thread "main" java.lang.RuntimeException: java.io.IOException: unexpected exception type at LambdaSerializationTest.serializeDeserialize(LambdaSerializationTest.java:69) at LambdaSerializationTest.giveFunction(LambdaSerializationTest.java:53) at LambdaSerializationTest.main(LambdaSerializationTest.java:48) Caused by: java.io.IOException: unexpected exception type at java.io.ObjectStreamClass.throwMiscException(java.base@9-ea/ObjectStreamClass.java:1559) at java.io.ObjectStreamClass.invokeReadResolve(java.base@9-ea/ObjectStreamClass.java:1156) at java.io.ObjectInputStream.readOrdinaryObject(java.base@9-ea/ObjectInputStream.java:1835) at java.io.ObjectInputStream.readObject0(java.base@9-ea/ObjectInputStream.java:1371) at java.io.ObjectInputStream.readObject(java.base@9-ea/ObjectInputStream.java:371) at LambdaSerializationTest.serializeDeserialize(LambdaSerializationTest.java:66) ... 2 more Caused by: java.lang.reflect.InvocationTargetException at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@9-ea/Native Method) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@9-ea/NativeMethodAccessorImpl.java:62) at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@9-ea/DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(java.base@9-ea/Method.java:533) at java.lang.invoke.SerializedLambda.readResolve(java.base@9-ea/SerializedLambda.java:230) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@9-ea/Native Method) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@9-ea/NativeMethodAccessorImpl.java:62) at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@9-ea/DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(java.base@9-ea/Method.java:533) at java.io.ObjectStreamClass.invokeReadResolve(java.base@9-ea/ObjectStreamClass.java:1150) ... 6 more Caused by: java.lang.ClassCastException: LambdaSerializationTest$ConverterB (in module: Unnamed Module) cannot be cast to LambdaSerializationTest$ConverterA (in module: Unnamed Module) at LambdaSerializationTest.$deserializeLambda$(LambdaSerializationTest.java:7) ... 16 more
11-07-2016

Changing priority to P3, issue is being discussed over http://stackoverflow.com/questions/38235351/java8-lambda-deserialization-classcastexception
11-07-2016