Standard Java serializiation of lambda expressions isn’t straight forward, but well understood. But the mechanism only works if the functional interface implements Serializable. When using alternative serialization libraries such as Kryo, there is typically no need to inherit from Serializable. Unfortunately I haven’t seen a solution yet to apply the same principle to lambdas.

For lambdas of functional interfaces which extend Serializable, there is the ClosureSerializer for Kyro. It works by invoking the writeReplace() function of the lambda to obtain a SerializedLambda which is then serialized. For deserialization the SerializedLambda is instantiated and the readResolve() method is invoked, resulting in the construction of the corresponding lambda. If the lambda is non-serializable, two key parts are missing: first the writeReplace() method is not present and second the readResolve() method does not work, as it depends on the synthetic $deserializeLambda$()-method created by the compiler in the class declaring the lambda.

For the writeReplace() function to be generated we can reuse a technique introduced in a previous post: the ‘InnerClassLambdaMetafactory’, responsible to generate the lambda classes, is simply patched to always treat the lambda as serializalbe and thus create the required method. This is simply achieved by overriding a constructor argument:

public class LambdaFactoryAgent {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        premain(agentArgs, inst);
    }

    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new InnerClassLambdaMetafactoryTransformer(), true);
        try {
            inst.retransformClasses(Class.forName("java.lang.invoke.InnerClassLambdaMetafactory"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static final class InnerClassLambdaMetafactoryTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            if (className.equals("java/lang/invoke/InnerClassLambdaMetafactory")) {
                ClassReader cr = new ClassReader(classfileBuffer);
                ClassWriter cw = new ClassWriter(cr, 0);
                cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
                    @Override
                    public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                            String[] exceptions) {
												// only modify the (only) constructor
                        if ("<init>".equals(name)) {
                            return new MethodVisitor(Opcodes.ASM5,
                                    super.visitMethod(access, name, desc, signature, exceptions)) {
                                @Override
                                public void visitCode() {
                                    super.visitCode();
                                    // set the isSerializable-parameter to true
                                    mv.visitInsn(Opcodes.ICONST_1);
                                    mv.visitVarInsn(Opcodes.ISTORE, 7);
                                };
                            };
                        } else
                            return super.visitMethod(access, name, desc, signature, exceptions);
                    }
                }, 0);
                return cw.toByteArray();
            }
            return null;
        }
    }
}

Remains the instantiation of the lambda from the SerializedLambda: Here we perform the invocation of the LambdaMetafactory directly instead of relying on the generated $deserializeLambda$() method. This is possible by simply extracting the required parameters from the SerializedLambda, invoking the meta factory and calling the resulting call site:

SerializedLambda lambda = ...;
Class<?> capturingClass = (Class<?>) capturingClassGetter.invoke(lambda);
ClassLoader cl = capturingClass.getClassLoader();
Class<?> implClass = cl.loadClass(lambda.getImplClass().replace('/', '.'));
Class<?> interfaceType = cl.loadClass(lambda.getFunctionalInterfaceClass().replace('/', '.'));
Lookup lookup = getLookup(implClass);
MethodType implType = MethodType.fromMethodDescriptorString(lambda.getImplMethodSignature(),
        cl);
MethodType samType = MethodType
        .fromMethodDescriptorString(lambda.getFunctionalInterfaceMethodSignature(), null);

MethodHandle implMethod;
boolean implIsInstanceMethod = true;
switch (lambda.getImplMethodKind()) {
case MethodHandleInfo.REF_invokeInterface:
case MethodHandleInfo.REF_invokeVirtual:
    implMethod = lookup.findVirtual(implClass, lambda.getImplMethodName(), implType);
    break;
case MethodHandleInfo.REF_invokeSpecial:
    implMethod = lookup.findSpecial(implClass, lambda.getImplMethodName(), implType, implClass);
    break;
case MethodHandleInfo.REF_invokeStatic:
    implMethod = lookup.findStatic(implClass, lambda.getImplMethodName(), implType);
    implIsInstanceMethod = false;
    break;
default:
    throw new RuntimeException("Unsupported impl method kind " + lambda.getImplMethodKind());
}

// determine type of factory
MethodType factoryType = MethodType.methodType(interfaceType, Arrays.copyOf(
        implType.parameterArray(), implType.parameterCount() - samType.parameterCount()));
if (implIsInstanceMethod)
    factoryType = factoryType.insertParameterTypes(0, implClass);


// determine type of method with implements the SAM
MethodType instantiatedType = implType;
if (implType.parameterCount() > samType.parameterCount())
	 instantiatedType = implType.dropParameterTypes(0,
					 implType.parameterCount() - samType.parameterCount());

// call factory
CallSite callSite = LambdaMetafactory.altMetafactory(lookup,
        lambda.getFunctionalInterfaceMethodName(), factoryType, samType, implMethod, instantiatedType, 1);

// invoke callsite
Object[] capturedArgs = new Object[lambda.getCapturedArgCount()];
for (int i = 0; i < lambda.getCapturedArgCount(); i++) {
    capturedArgs[i] = lambda.getCapturedArg(i);
}
return callSite.dynamicInvoker().invokeWithArguments(capturedArgs);

With this functionality in place, it is easy to make Kryo support non-serializable lambdas:

kryo.register(java.lang.invoke.SerializedLambda.class);
kryo.register(ClosureSerializer.Closure.class, new ClosureSerializer() {
	 @Override
	 public Object read(Kryo kryo, Input input, Class type) {
			 try {
					 SerializedLambda lambda = kryo.readObject(input, SerializedLambda.class);
					 ...
				 } catch (Throwable e) {
				 throw new RuntimeException(e);
		 }
 };
});

Of course, the agent has to be loaded during application startup:

AgentLoader.loadAgentClass(LambdaFactoryAgent.class.getName(), "");

That’s it, you are now ready to use lambdas without (Runnable & Serializable) ()->{...} - workarounds. I’m using this technique together with Hazelcast for distributed execution.