graal icon indicating copy to clipboard operation
graal copied to clipboard

[GR-65669] [Native Image] Build fails with GraalVM 24, but works with GraalVM 17

Open fabianiacob opened this issue 6 months ago • 3 comments

Describe the Issue

I've opened an issue first on Spring Boot, but seems the issue is related with GraalVM. Here is the issue: https://github.com/spring-projects/spring-boot/issues/45676#issuecomment-2915660689

After upgrading the dependencies the build started to fail with:

2689 | [INFO]     [creator]     Fatal error: java.lang.NoClassDefFoundError: reactor/core/publisher/Mono
2690 | [INFO]     [creator]     	at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
2691 | [INFO]     [creator]     	at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3035)
2692 | [INFO]     [creator]     	at java.base/java.lang.Class.getDeclaredMethod(Class.java:2422)
2693 | [INFO]     [creator]     	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.reflect.ReflectionDataBuilder.lambda$registerMethodLookup$0(ReflectionDataBuilder.java:500)
2694 | [INFO]     [creator]     	at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.reflect.ReflectionDataBuilder.lambda$runConditionalInAnalysisTask$1(ReflectionDataBuilder.java:184)
2695 | [INFO]     [creator]     	at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.CompletionExecutor.executeCommand(CompletionExecutor.java:166)
2696 | [INFO]     [creator]     	at org.graalvm.nativeimage.pointsto/com.oracle.graal.pointsto.util.CompletionExecutor.lambda$executeService$0(CompletionExecutor.java:152)

Which is something that shouldn't be used at all, since I don't have that class on the classpath. Like it's said on the other issue, with GraalVM 17, the build works, so seems an issue related to GraalVM. During my investigation running the agent, I see that spring boot uses multiple times the method Class.forName in order to check if the reactor package is present. From my perspective seems that GraalVM is trying to register all the classes that during the run with the agent were called with Class.forName, but if that returns a class not found exception, the hint shouldn't be generated for the native image.

Using the latest version of GraalVM can resolve many issues.

GraalVM Version

java 24.0.1 2025-04-15 Java(TM) SE Runtime Environment Oracle GraalVM 24.0.1+9.1 (build 24.0.1+9-jvmci-b01) Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 24.0.1+9.1 (build 24.0.1+9-jvmci-b01, mixed mode, sharing)

Operating System and Version

Windows -> But build with docker paketobuildpacks/builder-jammy-tiny

Build Command

<plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <env>
                            <BP_JVM_VERSION>24</BP_JVM_VERSION>
                            <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
                            <BP_NATIVE_IMAGE_BUILD_ARGUMENTS>
                                --initialize-at-build-time=com.google.protobuf.RuntimeVersion
                            </BP_NATIVE_IMAGE_BUILD_ARGUMENTS>
                            <BP_SPRING_AOT_ENABLED>true</BP_SPRING_AOT_ENABLED>
                            <BP_JVM_CDS_ENABLED>true</BP_JVM_CDS_ENABLED>
                            <BP_MAVEN_ACTIVE_PROFILES>native</BP_MAVEN_ACTIVE_PROFILES>
                        </env>
<!--                        <builder>paketobuildpacks/builder-jammy-tiny</builder>-->
                        <name>${artifactPath}:latest</name>
                        <tags>
                            <tag>${artifactPath}:${commitSha}</tag>
                        </tags>
                    </image>
                    <excludes>
                        <exclude>
                            <groupId>org.hibernate.orm</groupId>
                            <artifactId>hibernate-jpamodelgen</artifactId>
                        </exclude>
                        <exclude>
                            <groupId>org.immutables</groupId>
                            <artifactId>value</artifactId>
                        </exclude>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                        <exclude>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>

Expected Behavior

To build successfully and not try to register Mono class for reflection. Since was never on the classpath, and is not used inside my app.

Actual Behavior

2689 | [INFO] [creator] Fatal error: java.lang.NoClassDefFoundError: reactor/core/publisher/Mono

Steps to Reproduce

  1. You can use from the linked issue the example pom, or the reproducer zip file.
  2. Native build (direct or inside a docker container)

Additional Context

No response

Build Log Output and Error Messages

No response

fabianiacob avatar May 28 '25 09:05 fabianiacob

This error easy to reproduce is super difficult to diagnose. The best I had was by adding io.projectreactor:reactor-core dependency and -H:AbortOnTypeReachable=reactor.core.publisher.Mono which gave me the following error message: "Type reactor.core.publisher.Mono is marked as reachable str: Is used by annotation of element registered for reflection".

So I think one of the classes referenced by the generated reflect-config.json has an annotation referencing Mono and that breaks the native compilation. At minimum GraalVM should print more context about the faulty entry in the logs, maybe even be lenient in this case and not break.

sdeleuze avatar May 28 '25 10:05 sdeleuze

Hi @fabianiacob,

Thank you for reaching out to us about this. Could you please share a concise reproducer using a github repo with us? (I'm afraid I can't download the zip file you shared) alongside the steps needed to encounter the issue. Thank you.

selhagani avatar May 28 '25 12:05 selhagani

Hello @selhagani , thank you for working on it. Sure, I've just created the repo https://github.com/fabianiacob/spring-test-native

FYI: I've tried building with GraalVM 21, and also works.

fabianiacob avatar May 28 '25 12:05 fabianiacob

If a bit more minimal of a sample would help the GraalVM team, my colleague @jonatan-ivanov made https://github.com/jonatan-ivanov/micrometer-tracing-gh-1096, as we had the same issue reported to us in https://github.com/micrometer-metrics/tracing/issues/1096. It works with GraalVM 21 but not with GraalVM 24. What is the best way of figuring out from where exactly the native image build is determining the Mono type is reachable? I could potentially make an even more minimal sample if it would be helpful.

shakuzen avatar Jun 19 '25 03:06 shakuzen

Just a guess on the root cause, but in https://github.com/micrometer-metrics/tracing/issues/1096#issuecomment-2983229260 I noticed ReactiveSecurityContextHolderThreadLocalAccessor is loaded by a ServiceLoader and has Mono as part of its signature. Is it possible this is causing Mono to be considered reachable on GraalVM 24 while it wasn't on GraalVM 21?

shakuzen avatar Jun 19 '25 04:06 shakuzen

Bonjour @loicottet

Est-ce que l'exemple que l'équipe Micrometer a donné te permet d'avancer dans la résolution de ce petit hic?

ryleighmikami avatar Jun 24 '25 02:06 ryleighmikami

This issue has been fixed by https://github.com/oracle/graal/pull/10482 and will be part of GraalVM 25.0.

loicottet avatar Jun 30 '25 09:06 loicottet