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 --exact
Next, 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 -y
xcode-select --install
When 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 package
The 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.jar
programName 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.jar
Again, 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/programName
JavaFX 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.Main
programName 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/programName
programName 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.