graal icon indicating copy to clipboard operation
graal copied to clipboard

[GR-54965] objectInputStream.readObject() issue with springboot 3.3.0 + graalvm22 as native image

Open KafkaProServerless opened this issue 1 year ago • 16 comments

Hello team,

I would like to reach out regarding a small issue.

I have a very simple piece of code in my springboot 3.3 application.

The piece of code is:

 private static Map<?, ?> getNormalizedMap(final String encoded) {
        try (var objectInputStream = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(encoded)))) {
            return (Map<?, ?>) objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException | IllegalArgumentException e) {
            LOGGER.error("encoded={}", encoded, e);
            return Map.of();
        }
    }

This code would run fine on non native image, battle tested in production.

Now, we successfully built a native image.

However, at run time, we would get an issue saying we need to add this java.util.Collections$UnmodifiableMap in this file serialization-config.json, for line: return (Map<?, ?>) objectInputStream.readObject();

We followed the instructions, not knowing if the instructions were correct to begin with.

After following the instructions, we get this error message:

java.io.InvalidClassException: java.util.HashMap; local class incompatible: stream classdesc serialVersionUID = 362498820763181265, local class serialVersionUID = -3563561681480877083
        at [email protected]/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:598)
        at [email protected]/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2063)
        at [email protected]/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1912)
        at [email protected]/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2237)
        at [email protected]/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1747)
        at [email protected]/java.io.ObjectInputStream$FieldValues.<init>(ObjectInputStream.java:2603)
        at [email protected]/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2454)
        at [email protected]/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2269)
        at [email protected]/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1747)
        at [email protected]/java.io.ObjectInputStream.readObject(ObjectInputStream.java:525)
        at [email protected]/java.io.ObjectInputStream.readObject(ObjectInputStream.java:483)

Therefore, I would like to reach out, asking for help on this issue which is happening only with native images.

How to make return (Map<?, ?>) objectInputStream.readObject(); work with native image?

Thank you for your time and good day!

KafkaProServerless avatar May 15 '24 14:05 KafkaProServerless

Thanks for looking into this @fernando-valdez

KafkaProServerless avatar May 16 '24 02:05 KafkaProServerless

Hi @KafkaProServerless, can you please share more details on how you are building the native image? Are you using the config agent? or are you using the native image gradle/maven plugin?

Also, what is the exact version of GraalVM you are using? If this is not the latest version, can you please try the latest version and share of the problem still happens?

fernando-valdez avatar May 16 '24 02:05 fernando-valdez

Sure, so, I am using the latest springBoot 3.3.0-RC1. I am building using maven, with the command mvn -Pnative spring-boot:build-image. The graal VM version used is 22.0.1, which I think is very very very recent as of this writing.

Please let me know if I need to provide anything else @fernando-valdez

KafkaProServerless avatar May 16 '24 02:05 KafkaProServerless

Thanks. Can you please provide a small project (reproducer) that I can use to reproduce this issue locally? Also, what OS are you using?

fernando-valdez avatar May 16 '24 03:05 fernando-valdez

Sure, and again thank you for looking into this.

So, here is the setup: I am using ubuntu 22.04.4 LTS

Could you please:

  1. download graalissue.zip and unzip it
  2. cd inside the folder graalissue (which is a very small one file springboot project)
  3. just run mvn clean install and run the jar
  4. at this point, make a http request, something like curl http://localhost:8080/graalissue
  5. you should see this output: "It should be 41 here => 41"

Could you please let me know if you got to this point without issue? If yes, that would mean the code is working correctly in non native code If not, I believe it is better to debug a bit before proceeding.

KafkaProServerless avatar May 16 '24 07:05 KafkaProServerless

Once done, could you just do perform mvn clean

from here:

  1. please run mvn -Pnative spring-boot:build-image this would create the graalvm native image
  2. If the image is successfully created, can you run docker tag docker.io/library/graalissue:0.0.1-SNAPSHOT graalissue:latest
  3. from there, just run docker run -p 8080:8080 graalissue:latest
  4. same curl again: curl http://localhost:8080/graalissue
  5. and you should see something in the log like:
java.io.InvalidClassException: java.util.HashMap; local class incompatible: stream classdesc serialVersionUID = 362498820763181265, local class serialVersionUID = -3563561681480877083
        at [email protected]/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:598)
        at [email protected]/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2063)
        at [email protected]/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1912)
        at [email protected]/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2237)
        at [email protected]/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1747)
        at [email protected]/java.io.ObjectInputStream$FieldValues.<init>(ObjectInputStream.java:2603)
        at [email protected]/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2454)
        at [email protected]/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2269)
        at [email protected]/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1747)
        at [email protected]/java.io.ObjectInputStream.readObject(ObjectInputStream.java:525)
        at [email protected]/java.io.ObjectInputStream.readObject(ObjectInputStream.java:483)

Could you please try this and let me know if you reproduce as well?

KafkaProServerless avatar May 16 '24 07:05 KafkaProServerless

Please let me know if the example is not clear enough @fernando-valdez

KafkaProServerless avatar May 16 '24 23:05 KafkaProServerless

Any update please @fernando-valdez

KafkaProServerless avatar May 21 '24 01:05 KafkaProServerless

Thanks for your patience, but I don't think this is a direct issue from the native image. The native image has a closed-world assumption: When using Spring Boot to create native images, a closed-world is assumed, which restricts the dynamic aspects of the application. So serialization can be a sensitive issue here.

The error you see is caused because you are using Java serialization and have two different versions of the same class.

Here is an extract from the java docs:

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java Object Serialization Specification. This specification defines the serialVersionUID of an enum type to be 0L. However, it is strongly recommended that all serializable classes other than enum types explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization.

So you can try to refactor your code to explicitly set the value of the serialVersionUID attribute.

I hope this helps!

fernando-valdez avatar May 22 '24 02:05 fernando-valdez

Thank you for the answer. May I ask if you managed to reproduce the issue on your side? @fernando-valdez

And are you suggesting I should refactor java.util.Map ?

KafkaProServerless avatar May 22 '24 06:05 KafkaProServerless

@fernando-valdez we should first reproduce this issue, then we should run this with -H:ThrowMissingRegistrationErrors= and see if the serialization config is missing for some of the classes. Could you please work with @KafkaProServerless to get the reproducer?

vjovanov avatar Jun 19 '24 14:06 vjovanov

Hello @fernando-valdez ,

Please let me know what you need from my side, I will be glad to assist. The issue is still reproducible as of right now

KafkaProServerless avatar Jun 22 '24 23:06 KafkaProServerless

Hello @KafkaProServerless, let me share my updates:

  • I was able to build and run the jar file successfully, The output was It should be 41 here => 41
  • I was able to build and run the native image. And the output was It should be 41 here => 0
  • Used the -H:ThrowMissingRegistrationErrors= flag to get more information on the classes that require atention, and I got this error:
Exception in thread "main" org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively access class

   ch.qos.logback.classic.BasicConfigurato
  • I added that that class to the reflect-config.json file, but then I got another error:
Exception in thread "main" java.lang.IllegalArgumentException: Unable to instantiate factory class [org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer] for factory type [org.springframework.context.ApplicationContextInitializer]
        at org.springframework.core.io.support.SpringFactoriesLoader$FailureHandler.lambda$throwing$0(SpringFactoriesLoader.java:647)
        at org.springframework.core.io.support.SpringFactoriesLoader$FailureHandler.lambda$handleMessage$3(SpringFactoriesLoader.java:671)
        at org.springframework.core.io.support.SpringFactoriesLoader.instantiateFactory(SpringFactoriesLoader.java:231)
        at org.springframework.core.io.support.SpringFactoriesLoader.load(SpringFactoriesLoader.java:206)
        at org.springframework.core.io.support.SpringFactoriesLoader.load(SpringFactoriesLoader.java:160)
        at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:483)
        at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:479)
        at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:294)
        at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:273)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343)
        at com.example.graalissue.GraalissueApplication.main(GraalissueApplication.java:21)
        at [email protected]/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively access
  • Then I tried to use the agent to get all the config file. I used this: java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image -jar target/graalissue-0.0.1-SNAPSHOT.jar And I got this:
Logging system failed to initialize using configuration from 'null'
com.oracle.svm.core.jdk.resources.MissingResourceRegistrationError: The program tried to access the resource at path

   META-INF/spring/logback-pattern-rules

fernando-valdez avatar Jun 27 '24 03:06 fernando-valdez

Created internal ticket: GR-54965

fernando-valdez avatar Jun 27 '24 03:06 fernando-valdez

@fernando-valdez can you try adding META-INF/spring/logback-pattern-rules file to resources-config.json as the message suggests?

vjovanov avatar Jul 01 '24 07:07 vjovanov