You don't use the Java JDK when building native-images. Instead, you download GraalVM from their web site and it is packaged just like a JDK, complete with all of the usual JDK commands and modules etc. It needs to be set up as your JDK.
Maven of course is also necessary for this discussion but Graal can be compiled with Gradle as well.
You need some VisualStudio Build tools:
winget install Microsoft.VisualStudio.2022.BuildTools --exactNext, launch the Visual Studio Installer. Then click on:
- Workloads
- Desktop development with C++
Then on the right, make sure these are all checked (if these versions are not there use the latest):
- MSVC v143 - VS 2022 C++ x64/x86 build tools
- Windows 11 SDK
- C++ CMake tools for Windows
- Testing tools core features - Build Tools
- C++ AddressSanitizer
- C++ ATL for latest vXXX build tools...
- C++ MFC for latest vXXX build tools (x86...
Select whether to install while downloading (for fast internet connections) or not, then click on Modify and wait for everything to install.
sudo apt install curl -y
sudo apt install zip -y
sudo apt install zlib1g-dev -y
sudo apt install build-essential -yxcode-select --installWhen your project isn't modular and it doesn't have a GUI, the process is fairly straight forward. Generally, the steps are:
- Adding the Maven Assembly plugin to your build section
- Build the fat jar
- Use the GraalVM native-image-agent to build the reflection and various other config json files
- Compiling the native-image
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<archive>
<manifest>
<mainClass>${mainClass}</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>fat-jar</descriptorRef>
</descriptorRefs>
<finalName>${artifactId}</finalName>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>mvn clean packageThe way I like to do this is to make a bash script or a Windows bat file so that it handles things cleanly. It would look something like this:
#!/bin/bash
JP="$HOME/path/to/project/folder"
G="$JP/graalvm"
T="$JP/target"
mvn -f $JP/pom.xml clean package
java --enable-preview \
-agentlib:native-image-agent=config-merge-dir=$G \
-jar $T/programName-fat-jar.jarprogramName would need to match whatever the artifactId value is in the POM file.
This will run the program and the developer needs to engage the program so that anywhere it uses reflection or other calls are all actualized because GraalVM will read those calls and create the json files into the graalvm folder in the project root.
Stop the program once the dev has run it through all of its code.
I also do this with a bash script or a bat file
JP="$HOME/JetBrainsProjects/IntelliJIdea/iGet"
G="$JP/graalvm"
T="$JP/target"
mvn -f $JP/pom.xml clean package
native-image \
--no-fallback \
--verbose \
--enable-preview \
-H:+UnlockExperimentalVMOptions \
-H:+ReportExceptionStackTraces \
-H:JNIConfigurationFiles=$G/jni-config.json \
-H:DynamicProxyConfigurationFiles=$G/proxy-config.json \
-H:ReflectionConfigurationFiles=$G/reflect-config.json \
-H:ResourceConfigurationFiles=$G/resource-config.json \
-H:SerializationConfigurationFiles=$G/serialization-config.json \
-H:Name=$T/programName \
-jar $T/programName-fat-jar.jarAgain, programName would need to match whatever the artifactId value is in the POM file.
This should compile everything into the native image which will end up in target/programName
Simply run the program
target/programNameJavaFX applications need to be full modularized programs, which means there must be a module-info.java file in src/main/java and it must properly have all of the requires opens exports etc.
The steps are generally the same as above, only instead of using the fat jar, we're going to compile a modularized jar file using the Maven Dependency plugin in the build section
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/modules</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>This will put all of the modules into a folder that we can then use for the native-image command by adding it as a module-path
It will also create a jar file named programName-version.jar which we will use as the jar to compile into the native-image and we will also use it as a module-path.
We first generate all of our json configuration files like we did before only using a slightly different method
#!/bin/bash
JP="$HOME/path/to/project/folder"
G="$JP/graalvm"
T="$JP/target"
mvn -f $JP/pom.xml clean install
java \
--enable-preview \
-agentlib:native-image-agent=config-merge-dir=$G \
--module-path $T/programName-version.jar:$T/modules \
-m SimpleOTP/com.xyz.MainprogramName would need to match whatever the artifactId value is in the POM file
version needs to match whatever the version value is in the POM file.
com.xyz.Main needs to match whatever the mainClass value is in the POM file
Run the program through it's code then quit the program then build the native image like this:
#!/bin/bash
JP="$HOME/path/to/project/folder"
G="$JP/graalvm"
T="$JP/target"
mvn -f $JP/pom.xml clean install
native-image \
--no-fallback \
--verbose \
--enable-preview \
--module-path $T/programName-version.jar:$T/modules \
--module moduleName/com.xyz.Main \
-H:+UnlockExperimentalVMOptions \
-H:+ReportExceptionStackTraces \
-H:JNIConfigurationFiles=$G/jni-config.json \
-H:DynamicProxyConfigurationFiles=$G/proxy-config.json \
-H:ReflectionConfigurationFiles=$G/reflect-config.json \
-H:ResourceConfigurationFiles=$G/resource-config.json \
-H:SerializationConfigurationFiles=$G/serialization-config.json \
-H:Name=$T/programNameprogramName would need to match whatever the artifactId value is in the POM file
version needs to match whatever the version value is in the POM file.
com.xyz.Main needs to match whatever the mainClass value is in the POM file
moduleName needs to match whatever name is given in the first line of the module-info.java file. I like to use the programName in my module-info.java file to keep it simple.
##Comments
It is highly likely that you will encounter problems or errors during the native-image compile phase or even if the native-image compiles successfully but then the code does something that wasn't able to be caught during the native-image-agent inspection step.
What I find to be very helpful is to copy the complete error stack traces and give them to Chat GPT (openai.com) which seems to know a lot about the GraalVM native-image compile process.