javaserializationlambdaruntimebytecode

Identical Java SerializedLambda returns different result for implMethodKind


I'm Ronald, the author of JobRunr. JobRunr is a background job scheduling library that uses SerializedLambda and ASM to analyze a Java 8 lambda and converts it to a background job.

Recently, an error was reported and I tried reproducing it in JobRunr so that I can write a test to prevent regression.

The funny thing is that on the same Java version (17.0.2), I cannot reproduce it even if I copy the exact code.

In this project, the generated SerializedLambda has an implMethodKind equal to 5 (REF_invokeVirtual).

Yet, in JobRunr itself, the generated SerializedLambda has an implMethodKind equal to 7 (REF_invokeSpecial).

The actual code to generate the SerializedLambda is as follows:

public class GeoService {
    Logger LOG = LoggerFactory.getLogger(GeoService.class);

    public void executeGeoTreeJob(JobContext jobContext, long geoNameId, UserId userId) {
        LOG.error("Running: " + geoNameId);
    }

    public void run() {
        LOG.error("Starting job");
        UserId userId = new UserId();
        userId.setValue("test");
        long geoNameId = 1234;

        JobLambda jobLambda = () -> executeGeoTreeJob(JobContext.Null, geoNameId, userId);

        SerializedLambda serializedLambda = SerializedLambdaConverter.toSerializedLambda(jobLambda);
        System.out.println("=======");
        System.out.println("serializedLambda " + serializedLambda.getImplMethodKind());
        System.out.println("=======");

        BackgroundJob.enqueue(() -> executeGeoTreeJob(JobContext.Null, geoNameId, userId));
    }
}

In this project, the generated SerializedLambda has an implMethodKind equal to 5 (REF_invokeVirtual).

Yet, in JobRunr itself, the generated SerializedLambda has an implMethodKind equal to 7 (REF_invokeSpecial).

Why do I get different values for implMethodKind? Or, put differently, what do I need to do to the setup / JVM / ... to have the same results as in the example project.

Update:

I create the SerializedLambda as follows:

public class SerializedLambdaConverter {

    private SerializedLambdaConverter() {

    }

    public static <T> SerializedLambda toSerializedLambda(T value) {
        if (!value.getClass().isSynthetic()) {
            throw new IllegalArgumentException("Please provide a lambda expression (e.g. BackgroundJob.enqueue(() -> myService.doWork()) instead of an actual implementation.");
        }

        if (!(value instanceof Serializable)) {
            throw new JobRunrException("The lambda you provided is not Serializable. Please make sure your functional interface is Serializable or use the JobLambda interface instead.");
        }

        try {
            Method writeReplaceMethod = value.getClass().getDeclaredMethod("writeReplace");
            makeAccessible(writeReplaceMethod);
            return (SerializedLambda) writeReplaceMethod.invoke(value);
        } catch (Exception shouldNotHappen) {
            throw shouldNotHappenException(shouldNotHappen);
        }
    }
}

Below the output of javap:

Compiled from "GeoService.java"
public class org.jobrunr.tests.e2e.services.GeoService {
  org.slf4j.Logger LOG;
  public org.jobrunr.tests.e2e.services.GeoService();
  public void executeGeoTreeJob(org.jobrunr.jobs.context.JobContext, long, org.jobrunr.tests.e2e.services.UserId);
  public void run();
}

Attached the headers found in the output of javap -verbose (the full output is a bit too much for here in SO):

Classfile /Users/rdehuyss/Projects/Personal/jobrunr/jobrunr/tests/e2e-vm-jdk/build/classes/java/main/org/jobrunr/tests/e2e/services/GeoService.class
  Last modified 20 Feb 2024; size 4064 bytes
  SHA-256 checksum 595969292bcac503d33a4e54c1b2a4ffa7517f8aa6c50a8a1470400981e08ecb
  Compiled from "GeoService.java"
public class org.jobrunr.tests.e2e.services.GeoService
  minor version: 0
  major version: 52

Solution

  • Just to check, could you try and modify your .idea/compiler.xml, this part:

       <bytecodeTargetLevel target="17">
          <module name="JobRunr.tests.e2e-elasticsearch-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-elasticsearch-jackson.test" target="11" />
          <module name="JobRunr.tests.e2e-json-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-mariadb-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-mariadb-jackson.test" target="11" />
          <module name="JobRunr.tests.e2e-mongo-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-mongo-jackson.test" target="11" />
          <module name="JobRunr.tests.e2e-mysql-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-mysql-jackson.test" target="11" />
          <module name="JobRunr.tests.e2e-oracle-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-oracle-jackson.test" target="11" />
          <module name="JobRunr.tests.e2e-postgres-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-postgres-jackson.test" target="11" />
          <module name="JobRunr.tests.e2e-redis-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-redis-jackson.test" target="11" />
          <module name="JobRunr.tests.e2e-sqlserver-gson.test" target="11" />
          <module name="JobRunr.tests.e2e-sqlserver-jackson.test" target="11" />
          <module name="JobRunr.tests.e2e-ui.main" target="11" />
          <module name="JobRunr.tests.e2e-ui.test" target="11" />
          <module name="JobRunr.tests.e2e-vm-jdk.test" target="11" /> <----- here
        </bytecodeTargetLevel>