Native Executable

Let’s now produce a native executable for our application. It improves the startup time of the application and produces a minimal disk footprint. The executable would have everything to run the application including the JVM (shrunk to be just enough to run the application), and the application.

Native Image Process


We will be using GraalVM, which includes a native compiler for producing native images for a number of languages, including Java. Find more information on how to install GraalVM.

Ensure the location of the GraalVM installation is set by checking the appopriate environment variable:

That value should point to the installation of GraalVM. You can also test that it's installed correctly by running the included java executable:
$GRAALVM_HOME/bin/java -version

The output should be similar to the following (the relevant part is that it indicates it's using the GraalVM):

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)

Building the Native Image

Within the pom.xml is the declaration for the Maven profile named "native":

We use a profile because packaging the native image takes a few seconds. However, this compilation time is only incurred once, as opposed to every time the application starts, which is the case with other approaches for building and executing JARs.

Before building the native image, you need to know which container runtime you're going to use, either Docker or Podman. The Maven command requires this value (in lowercase) under the parameter quarkus.native.container-runtime. This example uses Podman, so if you're using Docker be sure to update the command accordingly.
From within the project directory, create the native executable by running:
mvn clean package -Pnative -DskipTests -Dquarkus.native.container-runtime=podman

In addition to the other JARs normally produced by the package goal, the native profile creates a file named getting-started-1.0.0-SNAPSHOT-runner.

Running the Native Image

As a native image, you simply run the file to start the application:
Notice the amazingly fast startup time:
INFO [io.quarkus] (main) Quarkus x.xx.x started in 0.044s. Listening on: http://[::]:8080 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
That's 44 milliseconds! Note that the startup time can be shown a bit differently on your environment.
And extremely low memory usage as reported by the Linux ps utility. Run the following command in another Terminal:
 261971 53740 ./target/getting-started-1.0.0-SNAPSHOT-runner

This shows that our process is taking around 53 MB of memory (Resident Set Size, or RSS). Pretty compact!

Note that the RSS and memory usage of any app, including Quarkus, will vary depending your specific environment, and will rise as the application experiences load.

In another terminal, ensure the application started correctly by using the URL from the previous examples:
curl localhost:8080/hello

Cleaning Up

Stop the native application by pressing CTRL-C in its terminal.

Next Steps

You've now built a Java application as an executable JAR and a Linux native binary. Now let's give our application superpowers by deploying to Kubernetes as a Linux container image.
Daniel Oh
Daniel Oh
Senior Principal Developer Advocate
Daniel Oh is a Senior Principal Developer Advocate at Red Hat. He works to evangelize building cloud-native microservices and serverless functions with cloud-native runtimes to developers. He also continues to contribute to various open-source cloud projects and ecosystems as a Cloud Native Computing Foundation (CNCF) ambassador for accelerating DevOps adoption in enterprises. Daniel also speaks at technical seminars, workshops, and meetups to elaborate on new emerging technologies for enterprise developers, SREs, platform engineers, and DevOps teams.