Search code examples
javafxquarkusgluongraalvm-native-imagespring-native

Native Executable with Quarkus or Springboot and JavaFx


I am trying to build a native executable for an app that works with Quarkus and JavaFx. The only way I have managed to achieve this has been marking lots of javaFx classes as --initialize-at-run-time, but this causes that, when trying to start the app, it fails with the following message:

java.lang.ClassNotFoundException: com.sun.javafx.tk.quantum.QuantumToolkit
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:71)
at java.lang.Class.forName(DynamicHub.java:1319)
at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:251)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.lang.Thread.run(Thread.java:829)
at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:567)
at com.oracle.svm.core.windows.WindowsJavaThreads.osThreadStartRoutine(WindowsJavaThreads.java:138)
2021-09-09 16:02:21,173 ERROR [io.qua.run.Application] (main) Failed to start application (with profile prod): java.lang.RuntimeException: No toolkit found
at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:273)                                              
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)                                               
at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)                                               
at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)                                          
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)                                    
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)                            
at java.lang.Thread.run(Thread.java:829)                                                                                
at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:567)                                      
at 
com.oracle.svm.core.windows.WindowsJavaThreads.osThreadStartRoutine(WindowsJavaThreads.java:138)   

I guess I need to add the javafx modules before build, but don't know how to achieve this from Maven. If anyone can help me I would be very grateful. Thanks in advance.

PS: If someone knows an alternative solution using springboot please share :)

Updated: Tested with SpringBoot and also fails with spring-native and gluonfx.

Made a minimal reproducible you can download from: https://github.com/ikaro143/JavaFx-SpringBoot-example/tree/master

The maven commands should be executed in the VisualStudio Native Tools Command Promp

To build with spring plugin use: mvn clean package -Pnative

To build with gluonfx use: mvn clean gluonfx:build -Pnative-gluonfx

In both cases the .exe is built, but none of both works.

Executing spring compilation from the console throws this stackTrace (Execution of gluon build does not give any feedback but neither works):

Exception in thread "main" java.lang.RuntimeException: Application launch error
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:202)
        at java.lang.Thread.run(Thread.java:829)
        at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:567)
        at com.oracle.svm.core.windows.WindowsJavaThreads.osThreadStartRoutine(WindowsJavaThreads.java:138)
Caused by: java.lang.AssertionError: java.lang.ClassNotFoundException: javafx.scene.image.Image
        at com.sun.javafx.util.Utils.forceInit(Utils.java:868)
        at com.sun.javafx.tk.Toolkit.<clinit>(Toolkit.java:945)
        at com.oracle.svm.core.classinitialization.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:375)
        at com.oracle.svm.core.classinitialization.ClassInitializationInfo.initialize(ClassInitializationInfo.java:295)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:286)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:160)
        at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        ... 3 more
Caused by: java.lang.ClassNotFoundException: javafx.scene.image.Image
        at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:71)
        at java.lang.Class.forName(DynamicHub.java:1319)
        at com.sun.javafx.util.Utils.forceInit(Utils.java:865)
        ... 11 more

Updated: Sharing a minimal reproducible for Quarkus + Jafavx

https://github.com/ikaro143/JavaFx-Quarkus-example

To build with quarkus plugin use: mvn clean package -Pnative

To build with gluonfx use: mvn clean gluonfx:build -Pnative-gluonfx

The Quarkus way fails at analysis step. With several errors as below:

analysis:  45,621.43 ms,  3.89 GB                                             
Error: Unsupported features in 12 methods                                                                               
Detailed message:                                                                                                       
Error: Class initialization of com.sun.javafx.tk.quantum.PrismImageLoader2$AsyncImageLoader failed. Use the option --initialize-at-run-time=com.sun.javafx.tk.quantum.PrismImageLoader2$AsyncImageLoader to explicitly request delayed initialization of this class.                                                                                                    
Original exception that caused the problem: java.lang.ExceptionInInitializerError                                               
at java.base/jdk.internal.misc.Unsafe.ensureClassInitialized0(Native Method)                                            
at java.base/jdk.internal.misc.Unsafe.ensureClassInitialized(Unsafe.java:1042)                                          
at jdk.unsupported/sun.misc.Unsafe.ensureClassInitialized(Unsafe.java:698)                                              
at jdk.internal.vm.compiler/org.graalvm.compiler.serviceprovider.GraalUnsafeAccess.ensureClassInitialized(GraalUnsafeAccess.java:77)

The gluonfx way fails at setup step with this vague error:

[com.quarkusjavafx.example.applauncher.cdiapplication:9740]    classlist:   3,592.40 ms,  1.19 GB
[com.quarkusjavafx.example.applauncher.cdiapplication:9740]        (cap):   3,086.44 ms,  1.19 GB
[com.quarkusjavafx.example.applauncher.cdiapplication:9740]        setup:   6,095.10 ms,  1.19 GB
Fatal error:java.lang.NullPointerException
at java.base/java.io.Reader.<init>(Reader.java:167)
at java.base/java.io.InputStreamReader.<init>(InputStreamReader.java:72)
at io.quarkus.runtime.graal.ResourcesFeature.beforeAnalysis(ResourcesFeature.java:21)
at com.oracle.svm.hosted.NativeImageGenerator.lambda$runPointsToAnalysis$11(NativeImageGenerator.java:691)
at com.oracle.svm.hosted.FeatureHandler.forEachFeature(FeatureHandler.java:71)
at com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:691)
at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:532)
at com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:491)
at com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:380)
at com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:543)
at com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:119)
at com.oracle.svm.hosted.NativeImageGeneratorRunner$JDK9Plus.main(NativeImageGeneratorRunner.java:573)
[com.quarkusjavafx.example.applauncher.cdiapplication:9740]      [total]:   9,723.35 ms,  1.19 GB

Solution

  • Updated After a suggestion from José Pereda I have tried migrating the backend to Micronaut and have been able to build the native image successfully. The migration from springboot to micronaut was very easy to do. Just changing the parent in the pom and the basic dependencies.

    The native image is generated with the maven code: mvn clean gluonfx: build -Pnative-gluonfx

    Maybe you need to use first the goal gluonfx:runagent to generate the config files needed to run.

    In my experience I had some dependencies issues that only noticed when running the goal gluonfx:nativerun after the build was done. And then adjust the code.

    I leave here a minimal example in case someone needs it: https://github.com/ikaro143/example-micronaut

    PS: Be sure your main class is registered for reflection.