Compiling virtual thread applications into native executables

In another blog post, we have seen how you can implement a CRUD application with Quarkus to utilize virtual threads. This post will show how you can compile such an application into a native executable.

Installing GraalVM 21

To compile a Quarkus application leveraging virtual threads into a native executable, you need a GraalVM version supporting Java 21. You can download it from GitHub.

Alternatively, you can use the SDKMAN tool to install it:

> sdk install java 21-graalce
Downloading: java 21-graalce

In progress...

Repackaging Java 21-graalce...

Done repackaging...
Cleaning up residual files...

Installing: java 21-graalce
Done installing!

Do you want java 21-graalce to be set as default? (Y/n): n

Once installed, make sure the GRAALVM_HOME environment variable points to the GraalVM installation directory:

> export GRAALVM_HOME=$HOME/.sdkman/candidates/java/21-graalce

Compiling the application into a native executable

We will reuse the CRUD application developed in a previous blog post. The source code is located in the virtual-threads-demos GitHub repository. Note that while we are using the CRUD application, the same approach can be used with any Quarkus application leveraging virtual threads, including the other demos from the repository.

First make sure you use Java 21+ and that the GRAALVM_HOME environment variable points to the GraalVM installation directory.

Then, in the pom.xml file, add the native profile:

<profiles>
  <profile>
    <id>native</id>
      <activation>
        <property>
          <name>native</name>
        </property>
      </activation>
      <properties>
        <quarkus.package.type>native</quarkus.package.type>
      </properties>
  </profile>
</profiles>

The native profile is activated when the native property is set. So, compile the application with:

> mvn clean package -Dnative

The compilation takes a few minutes. Once done, you can run the application:

  • 1) First, start the database:

> docker run --ulimit memlock=-1:-1 -d -it --rm=true --memory-swappiness=0 \
    --name postgres-quarkus-demo -e POSTGRES_USER=restcrud \
    -e POSTGRES_PASSWORD=restcrud -e POSTGRES_DB=rest-crud \
    -p 5432:5432 postgres:15-bullseye
  • 2) Then, start the application:

> ./target/crud-example-1.0.0-SNAPSHOT-runner

You get:

> ./target/crud-example-1.0.0-SNAPSHOT-runner
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2023-10-17 09:44:34,925 INFO  [io.quarkus] (main) crud-example 1.0.0-SNAPSHOT native (powered by Quarkus 3.4.1) started in 0.072s. Listening on: http://0.0.0.0:8080
2023-10-17 09:44:34,925 INFO  [io.quarkus] (main) Profile prod activated.
2023-10-17 09:44:34,925 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, narayana-jta, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]

Then, open the application in a browser (http://localhost:8080) and start adding, updating, and completing tasks. You will see in the logs that the processing of these requests are executed on virtual threads:

2023-10-17 10:15:09,992 INFO  [org.acm.cru.TodoResource] (quarkus-virtual-thread-0) Called on VirtualThread[#78,quarkus-virtual-thread-0]/runnable@ForkJoinPool-5-worker-1
2023-10-17 10:15:13,136 INFO  [org.acm.cru.TodoResource] (quarkus-virtual-thread-1) Called on VirtualThread[#85,quarkus-virtual-thread-1]/runnable@ForkJoinPool-5-worker-1

Summary

This blog post explains how to compile a Quarkus application leveraging virtual threads into a native executable. First, make sure that you have a GraalVM installation supporting Java 21+. Then, add the native profile to the pom.xml file and compile the application using the -Dnative option. Finally, run it as any other native executable!