Building Native Image With Quarkus and GraalVM

In this blog post I will explain how to build native image with Quarkus framework on Windows and Linux.

One of the main features of Quarkus framework is support for the native image building. With Native Image feature, we can build self executable Java exe which does not require the JDK to run the program.

Quarkus provides support for compiling applications to native executables using the GraalVM native-image compiler.

What is Native Image

Native Image is a technology to ahead-of-time compile Java code to a standalone executable, called a native image. This executable includes the application classes, classes from its dependencies, runtime library classes, and statically linked native code from JDK. It does not run on the Java VM, but includes necessary components like memory management, thread scheduling, and so on from a different runtime system, called “Substrate VM”. Substrate VM is the name for the runtime components (like the deoptimizer, garbage collector, thread scheduling etc.). The resulting program has faster startup time and lower runtime memory overhead compared to a JVM.

Note

For building Native image in quarkus , you need computer/laptop with more than 8GB RAM. If you have less RAM , you might get OutOfMemoryError

Fatal error:org.graalvm.compiler.debug.GraalError: java.lang.OutOfMemoryError: GC overhead limit exceeded

In one of my previous blog post, I explained how to build Rest API using Quarkus framework. I am going to use the same project for building the native image.

First we need to install additional softwares for building the Native Images

i) Download and Install GraalVM CE 22.2.0 version ( Java 11/17 based) from here

Next we need to set up the environment variables

Set GRAALVM_HOME environment variable to the GraalVM installation directory

Linux

export GRAALVM_HOME=<GraalVM instllation directory>
Code language: Java (java)

Windows

Set environment variables from control panel

ii) Install Native Image Tools

Install the native-image tool using gu install

Linux

${GRAALVM_HOME}/bin/gu install native-image
Code language: Shell Session (shell)

Windows

%GRAALVM_HOME%\bin\gu install native-image
Code language: Shell Session (shell)

Linux

On Linux we need to install following libraries

# dnf (rpm-based) sudo dnf install gcc glibc-devel zlib-devel libstdc++-static # Debian-based distributions: sudo apt-get install build-essential libz-dev zlib1g-dev
Code language: Shell Session (shell)

Run the below command from project folder to create native image

mvnw package -Pnative
Code language: Shell Session (shell)

On Linux, native image can be created without the GraalVM by running the below command. Below command create native image in target folder.

./mvnw package -Pnative -Dquarkus.native.container-build=true
Code language: Shell Session (shell)

Let’s start the application by running the native image

$ ./target/quarkus-hello-world-1.0.0-SNAPSHOT-runner
Code language: Java (java)

Windows

Download and Install Visual Studio 2019 Visual C++ Build Tools and Visual Studio 2019 community edition

Note : According to Quarkus documentation to build native image we need to install only Visual Studio 2017 Visual C++ Build Tools but on my Windows 10 system, I could build native image only after installing 2019 Visual C++ Build Tools and Visual Studio Community Edition

Now search for x86_x64 cross tools command prompt for VS 2019 and open the command prompt by clicking on it.

Now navigate to project folder and run following command

mvnw package -Pnative
Code language: Shell Session (shell)

Note

As per Quarkus documentation, we can build the native image with the following command. without opening VS command prompt. But I it did not work for me.

cmd /c call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat" && mvn package -Pnative

The above command will create exe file in the target folder

The native executable will contain the application code, required libraries, Java APIs, and a reduced version of a VM. The smaller VM base improves the start up time of the application and produces a minimal disk footprint.

If we run the exe file, it will start the application at port 8080

Let’s test the REST API again

Building Docker image

We can build the Docker image of native image built in above step.

We can utilize the Docker file (Dockerfile.native) provided by starter project

docker build -f src/main/docker/Dockerfile.native -t quarkus/quarkus-hello-world .
Code language: Shell Session (shell)

Then run the container using:

docker run -i --rm -p 8080:8080 quarkus/quarkus-hello-world
Code language: Shell Session (shell)

Troubleshooting

Generating native image with GraalVm is time and memory consuming operation.

If you have less then 8GB ram , you might get following errors in your local system or CI/CD while building native image

Fatal error:org.graalvm.compiler.debug.GraalError: java.lang.OutOfMemoryError: GC overhead limit exceeded
Error: Image build request failed with exit status 137

To over come the problems , you can use following options. Remember that if you limit the memory consumption build time will increase.

  1. Limit the memory consumption by providing additional params to quarkus-maven-plugin.

<quarkus.native.additiona-build-args>-J-Xmx4G</quarkus.native.additiona-build-args>

<profile>
      <id>native</id>
      <activation>
        <property>
          <name>native</name>
        </property>
      </activation>
      <build>
        <plugins>
          <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>${surefire-plugin.version}</version>
            <executions>
              <execution>
                <goals>
                  <goal>integration-test</goal>
                  <goal>verify</goal>
                </goals>
                <configuration>
                  <systemPropertyVariables>
                    <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
                    <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                    <maven.home>${maven.home}</maven.home>
                  </systemPropertyVariables>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
      <properties>
        <quarkus.package.type>native</quarkus.package.type>
        <quarkus.native.additiona-build-args>-J-Xmx4G</quarkus.native.additiona-build-args>
      </properties>
    </profile>

2. you can limit memory usage from Quarkus.
In your src/main/resources/application.properties file, just set:

quarkus.native.native-image-xmx=4G

3. Pass the parameter during build process

mvn package -Dnative -Dquarkus.native.native-image-xmx=4G

References

Similar Posts