Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

GraalVM native image

An ordinary jar runs on the JVM: the virtual machine loads classes, and the JIT compiler turns hot code into machine code while the program runs. A native image works differently: the native-image tool from GraalVM compiles the application together with the needed part of the JDK ahead of time into a regular executable for a specific OS, like a C program. Such a file starts without an installed Java, in tens of milliseconds, and uses noticeably less memory.

Why Micronaut fits

When building a native image, GraalVM assumes a "closed world": everything the application can call must be known in advance. Reflection, dynamic proxies and loading classes by name break this assumption, and for them you have to write configuration files by hand. Micronaut hardly uses reflection: beans, routes, serialization and Micronaut Data queries are already generated at compile time. That is why most Micronaut applications build into a native image without extra configuration.

What you need

  1. GraalVM for Java 25 instead of a regular JDK. The easiest way is SDKMAN: sdk install java 25.4.4+1-graal, then sdk use java 25.4.4+1-graal.
  2. A C toolchain for your OS: gcc and developer libraries on Linux, Visual Studio Build Tools on Windows, Xcode Command Line Tools on macOS.
  3. A few gigabytes of free memory: the build is heavy.

Building

The generated project is already prepared: the native-maven-plugin comes through micronaut-parent. You only change the packaging type:

./mvnw package -Dpackaging=native-image

The build takes several minutes. The result is the file target/blog (target\blog.exe on Windows). You run it directly, without java:

./target/blog

The application answers on the same addresses and reads the same environment variables as the jar. There is also a container variant: ./mvnw package -Dpackaging=docker-native.

Jar or native file

Executable jarNative image
Needs Java on the serveryesno
Build timesecondsminutes
Startupabout a secondtens of milliseconds
Memorymoreless
One file for every OSyesno, built for each OS
Peak speed over a long runhigh thanks to the JITusually a little lower

A native image pays off where the application is started and stopped often: cloud functions (serverless), autoscaling in Kubernetes, command-line tools. For an ordinary long-running service, an executable jar is simpler and perfectly good.

How to check

Before a native build, make sure the tests pass on the JVM. Libraries that use reflection may need hints for GraalVM, which go into src/main/resources/META-INF/native-image/. For your own classes in Micronaut, @Serdeable and @Introspected are usually enough.

Common mistake

If you start a native build on a regular JDK, Maven stops at the native:compile-no-fork step:

[ERROR] Failed to execute goal org.graalvm.buildtools:native-maven-plugin:1.1.12:compile-no-fork (default-compile-no-fork)
on project blog: native-image is not installed in your JAVA_HOME. This probably means that the JDK at '...' is not
a GraalVM distribution. The GraalVM Native Maven Plugin requires GRAALVM_HOME or JAVA_HOME to be a GraalVM distribution.

Install GraalVM, point JAVA_HOME or GRAALVM_HOME to it, then check with native-image --version.

Thanks for reading!

Donate to the site — we work hard for you.