I am working on a small OpenJFX project that can be compiled into a native app. Here's a sample of the relevant parts of the source code:
Main.java
package app;
public class Main {
public static void main(String[] args) {
App.main(args);
}
}
App.java
package app;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class App extends Application {
private static Scene scene;
@Override
public void start(Stage stage) throws Exception {
scene = new Scene(loadFXML("MainPanel"));
stage.setScene(scene);
stage.show();
}
static void setRoot(String fxml) throws IOException {
scene.setRoot(loadFXML(fxml));
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.3</version>
<configuration>
<mainClass>app.App</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>app.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>app.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>19.3.0</version>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<skip>false</skip>
<imageName>app</imageName>
<buildArgs>
--no-fallback
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>13</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>13</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>13</version>
<type>jar</type>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>13</version>
<type>jar</type>
<classifier>linux</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>13</version>
<type>jar</type>
<classifier>mac</classifier>
</dependency>
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>19.3.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
The project successfully compiles into two .jar files and a native binary (in this specific case, a Linux x86_64 Glibc ELF binary). The non-standalone .jar (that is later compiled by GraalVM) successfully executes using java --module-path /usr/share/openjfx/lib --add-modules javafx.controls,javafx.fxml -jar target/app-1.0-SNAPSHOT.jar
. However, the generated native binary, launched as target/app
from the terminal, gives the following error:
Exception in thread "main" java.lang.RuntimeException: java.lang.ClassNotFoundException: app.App
at javafx.application.Application.launch(Application.java:304)
at app.App.main(App.java:32)
at app.Main.main(Main.java:6)
Caused by: java.lang.ClassNotFoundException: app.App
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.Class.forName(DynamicHub.java:1197)
at javafx.application.Application.launch(Application.java:292)
... 2 more
Is there something I'm doing wrong, is there some sort of silent compile error or missing parameters on the GraalVM side?
The issue at play is that GraalVM cannot compile standalone binaries containing classes with reflection unless told beforehand. This should be possible to setup using native-image-maven-plugin
, but I have instead switched to Gluon Client, which means I have made the following additions to pom.xml
after removing the no longer useful stuff:
<pluginRepositories>
<pluginRepository>
<id>gluon-releases</id>
<url>https://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>com.gluonhq</groupId>
<artifactId>client-maven-plugin</artifactId>
<version>0.1.10</version>
<configuration>
<mainClass>app.App</mainClass>
<reflectionList>
<list>javafx.fxml.FXMLLoader</list>
<list>app.SomeCustomClass</list>
</reflectionList>
</configuration>
</plugin>
</plugins>
</build>
Notice that app.SomeCustomClass
is an example of a class that returned a similar java.lang.ClassNotFoundException
error and javafx.fxml.FXMLLoader
was added for the same reason.