oracle / graal

GraalVM compiles Java applications into native executables that start instantly, scale fast, and use fewer compute resources 🚀
https://www.graalvm.org
Other
20.35k stars 1.63k forks source link

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

Open KafkaProServerless opened 5 months ago

KafkaProServerless commented 5 months ago

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 java.base@22.0.1/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:598)
        at java.base@22.0.1/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2063)
        at java.base@22.0.1/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1912)
        at java.base@22.0.1/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2237)
        at java.base@22.0.1/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1747)
        at java.base@22.0.1/java.io.ObjectInputStream$FieldValues.<init>(ObjectInputStream.java:2603)
        at java.base@22.0.1/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2454)
        at java.base@22.0.1/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2269)
        at java.base@22.0.1/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1747)
        at java.base@22.0.1/java.io.ObjectInputStream.readObject(ObjectInputStream.java:525)
        at java.base@22.0.1/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 commented 5 months ago

Thanks for looking into this @fernando-valdez

fernando-valdez commented 5 months ago

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?

KafkaProServerless commented 5 months ago

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

fernando-valdez commented 5 months ago

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

KafkaProServerless commented 5 months ago

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 commented 5 months ago

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 java.base@22.0.1/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:598)
        at java.base@22.0.1/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2063)
        at java.base@22.0.1/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1912)
        at java.base@22.0.1/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2237)
        at java.base@22.0.1/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1747)
        at java.base@22.0.1/java.io.ObjectInputStream$FieldValues.<init>(ObjectInputStream.java:2603)
        at java.base@22.0.1/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2454)
        at java.base@22.0.1/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2269)
        at java.base@22.0.1/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1747)
        at java.base@22.0.1/java.io.ObjectInputStream.readObject(ObjectInputStream.java:525)
        at java.base@22.0.1/java.io.ObjectInputStream.readObject(ObjectInputStream.java:483)

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

KafkaProServerless commented 5 months ago

graalissue.zip

KafkaProServerless commented 5 months ago

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

KafkaProServerless commented 5 months ago

Any update please @fernando-valdez

fernando-valdez commented 5 months ago

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!

KafkaProServerless commented 5 months ago

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 ?

vjovanov commented 4 months ago

@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?

KafkaProServerless commented 4 months ago

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

fernando-valdez commented 4 months ago

Hello @KafkaProServerless, let me share my updates:

fernando-valdez commented 4 months ago

Created internal ticket: GR-54965

vjovanov commented 3 months ago

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