"Invalid lambda deserialization" when lambda used for interface implementation
Asked Answered
G

2

4

I have a wicket project with lambda expressions. On one page when user clicks on back button my application crashes on:

java.lang.IllegalArgumentException: Invalid lambda deserialization
at x.y.z.MyPage$3.$deserializeLambda$(MyPage.java:1)

In the page class (where I return) I'm using lambda expression for implementation of this interface:

public interface Localizator extends Serializable {
    String getLocalizedString(String key);
}

And the lambda:

protected void someMethod() {
    localize((String key) -> getString(key));
}

When I change lambda to anonymous class, everything works fine. How should I use lambda in this case?

Env: Java 1.8.0_25, Netbeans 8.0.2, Wicket 6.17.0.

EDIT: This is real (but simplified) method with lambda:

@Override
protected DataLoader createDataLoader() {

    return new DataLoader(){

        @Override
        public List loadData() {
            ...
        }

        @Override
        public List convertToTableRows(List data) {
            return Converter.asRowList(
                data, 
                (Record record) -> {...}, // this lambda is OK
                (String key) -> getString(key)); // this lambda is crashing
        }

        @Override
        public List filterTableRow() {
            ...
        }

    };
}

Converter class:

public class Converter implements Serializable { 
    public static ArrayList asRowList(List data, OtherLoader loader, Localizator localizator){...}

DataLoader also extends Serializable.

Gamp answered 9/3, 2015 at 16:10 Comment(4)
How does your localize() method look like?Shena
Actually localize method is only an example, lambda is used in more complicated context, I will edit my question to show, how it is used in reality.Gamp
Yes, casting lambda this way (Localizator & Serializable)(String key) -> getString(key) helps, but I don't understand why I have to cast lambda of a serializable interface to serializable. Do I have to cast every lambda to serializable in every page? This makes no sense...Gamp
When the functional interface extends Serializable, no additional cast should be necessary. Ensure that the classes are in sync, e.g. that you didn’t compile MyPage.java against a version of the interface which didn’t extends Serializable yet and that you don’t try to deserialize using different versions of the MyPage class.Cession
C
8

To enable Serialization support, having an interface extending Serializable is sufficient. There is no need to do an additional cast then. This is indicated by the fact that your code could serialize the lambda expression instance. It failed when deserializing, which is an indicator for a class incompatibility, so if inserting the cast helped, it’s very likely, that it was a side effect because inserting the cast forced recompiling of the class containing the lambda expression.


If you want to continue using serialized lambdas, you should understand how it works, as explained in the documentation of SerializedLambda:

Implementors of serializable lambdas, such as compilers or language runtime libraries, are expected to ensure that instances deserialize properly. One means to do so is to ensure that the writeReplace method returns an instance of SerializedLambda, rather than allowing default serialization to proceed.

SerializedLambda has a readResolve method that looks for a (possibly private) static method called $deserializeLambda$(SerializedLambda) in the capturing class, invokes that with itself as the first argument, and returns the result. Lambda classes implementing $deserializeLambda$ are responsible for validating that the properties of the SerializedLambda are consistent with a lambda actually captured by that class.

So when the $deserializeLambda$ rejects the deserialization with an exception, it implies that the properties of the serialized lambda do not match the properties of a known serializable lambda defined inside the class. These properties may turn out to be very fragile.

They include the functional interface, the captured arguments and a target method. In case of a lambda expression (rather than a method reference), the target method is a synthetic method inside the defining class with a compiler-chosen, unspecified name that might depend even on other lambda expressions within the same class as inserting another expression might cause a name change due to a numbering scheme.

Unlike ordinary classes where inserting a serialVersionUID might tell the Serialization framework that it shouldn’t care about apparent inconsistencies, you can’t tell lambda expressions to show more robustness. In other words, once you have serialized lambda instances which ought to persist, you must not change the their defining class. See also this answer

Cession answered 10/3, 2015 at 9:36 Comment(0)
H
1

This does not answer the question above but this thread pops up first when searching for Invalid lambda deserialization in Flink.

If you use Proguard to obfuscate before publishing:

  • Proguard messes up the Serializable classes

  • You need to config proguard.pro to fix this error, see this answer.

In my own case I implemented the "Java 8 Lambda" settings suggested in the official documentation some time back but the documentation changed and now I was missing newer settings explained there.


What "lambda not serializable" means in general:

  • e.g. this lambda: .map(string -> (CustomClass) string)

  • is not serializable if CustomClass does not implement Serializable.

To make this class serializable add implements Serializable, add SerialVersionUID and ensure all attributes of that class are serializable themselves.

Halette answered 17/2, 2021 at 13:48 Comment(1)
I hope this way of "linking another question" is ok. This reference would have saved me one day trying to understand which lambda is not serializable when in fact everything was ok (only obfuscation broke things).Halette

© 2022 - 2024 — McMap. All rights reserved.