How to build a native application in Java
We all know that Java is an interpreted language and that when a Java program is compiled, we get bytecode files. These bytecode files are then interpreted at runtime by the Java Virtual Machine.
Other languages like Go or Rust are compiled directly into a native executable file. These files are usually much smaller than similar Java applications. As such, they require less memory and start much faster.
GraalVM to the rescue
GraalVM is a technology developed by Oracle that wants to change that. Among other things, you can use Graal to generate a native executable that can be run without a Java Virtual Machine. At compilation time, Graal will examine the application and determine what classes need to be included in the executable. It will then produce a native program, which can be run from the command line as any other program. No JVM is needed.
For this article, I will use IntelliJ Ultimate Edition. I think it should also work with the Community Edition, but I haven’t tested it.
But first, let’s see how you can quickly install GraalVM on a Mac. The simplest way is to use SDKMAN (should also work for Windows and Linux):
sdk install java 22.3.r17-grl
Once the installation is complete, run the java interpreter just to verify. You should get something like this:
$ java -version
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)
Installing GraalVM like this simply installs the core GraalVM compiler. Run the gu tool to find out what is installed:
$ gu list
ComponentId Version Component name Stability Origin
---------------------------------------------------------------------------------------------------------------------------------
graalvm 22.3.0 GraalVM Core Supported
You can check what is available with
$ gu available
Downloading: Component catalog from www.graalvm.org
ComponentId Version Component name Stability Origin
---------------------------------------------------------------------------------------------------------------------------------
espresso 22.3.0 Java on Truffle Experimental github.com
espresso-llvm 22.3.0 Java on Truffle LLVM Java librExperimental github.com
js 22.3.0 Graal.js Supported github.com
llvm 22.3.0 LLVM Runtime Core Supported github.com
llvm-toolchain 22.3.0 LLVM.org toolchain Supported github.com
native-image 22.3.0 Native Image Early adopter github.com
native-image-llvm-backend22.3.0 Native Image LLVM Backend Early adopter (experimental) github.com
nodejs 22.3.0 Graal.nodejs Supported github.com
python 22.3.0 GraalVM Python Experimental github.com
R 22.3.0 FastR Experimental github.com
ruby 22.3.0 TruffleRuby Experimental github.com
visualvm 22.3.0 VisualVM Experimental github.com
wasm 22.3.0 GraalWasm Experimental github.com
We are interested in the native-image component, so let’s install it with
gu install native-image
We are now ready to build our native project!
Fire up your copy of IntelliJ and create a new Project. Make sure you select the installed GraalVM. I used Gradle. I called my project nativejava:
IntelliJ will automatically create a simple Main class that prints Hello World under the org.example package.
Open the build.gradle file, remove the existing content and paste the following content:
plugins {
id 'application'
id 'java'
id 'org.graalvm.buildtools.native' version '0.9.16'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
application {
mainClass = 'org.example.Main'
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}
Refresh Gradle. The Graal build tools will be downloaded and there will be a couple of new tasks under build:
Run the nativeCompile task by double clicking on it. The GraalVM will kick in, compile the code and create a native executable in the build/native/nativeCompile folder. Open a terminal and cd to that folder. You should see the following:
$ ll
total 22560
-rwxr-xr-x 1 samaddison staff 11545344 Oct 29 20:30 nativejava
-rw-r--r-- 1 samaddison staff 25 Oct 29 20:30 nativejava.build_artifacts.txt
Yes! We now have an executable Java file called “nativejava”, which can be run like this:
$ ./nativejava
Hello world!
Of course, this is a very basic Java program, and things get trickier when you have an application that uses features like reflection. But there are ways to handle those situations. I might go over some examples in a future article.
Conclusion
Building a native image is only one of the things that you can do with GraalVM. Go and read about it. It is a truly amazing piece of technology and it is full of possibilities. You can build languages on top of Graal. In fact, they have early versions of Ruby, Python and R, and complete implementations of Java and Node.js, with very promising performance results.