Running MicroProfile reactive with Helidon Nima and Virtual Threads

I recently became interested in Helidon as part of my investigations into Java Loom. Indeed, version 4 is natively based on Virtual Threads. Before going any further, let’s introduce quickly Helidon.

Helidon is an Open Source (source on GitHub, Apache V2 licence) managed by Oracle that enables to develop lightweight cloud-native Java application with fast start up and warm up, lightweight runtime and built-in observability.

Two flavors are available:

  1. Helidon SE which offers a basic programming model with top performance,
  2. Helidon MicroProfile which offers a more elaborate programming model while maintaining very good performance.

At the risk of oversimplifying, Helidon is quite comparable to Quarkus. It’s not my intention to compare them here. Let’s just say I appreciate them both 😉

More on Helidon is provided in this FAQ.

The problem with Platform Threads

In a nutshell, Virtual Threads (VT) offer a new concurrency model implemented by the Java Virtual Machine (JVM). But what is the problem with traditional Java Threads?

This kind of threads, also known as Platform Threads (PT), tends to use and block many Operating System Threads (OST). Being managed in kernel mode, OST are heavy and precious resources. Having plenty of them blocked is clearly not optimal with modern multi-core CPUs.

In fact, it’s the use of traditional imperative programming (aka thread-per-request) that causes problem. Why is this? A PT is in fact a thin wrapper on top of an OST, with a 1-to-1 relationship. When an IO is pending (for instance a database or network call), the PT and it underlying OST are blocked waiting for the result.

In response to this problem, reactive programming offers a solution based on a simple principle: use few PT (typically the number of CPU cores) and never block them! However, it comes at the cost of added complexity in terms of code, test, debug and troubleshooting. Moving from imperative to reactive is a paradigm shift, and not all developers are comfortable with it.

Run reactive with Virtual Threads

VT propose another approach enabling to keep using imperative programming while running reactive. The good news is that VT will become standard with Java 21 (released on Sept. 19).

VT do not replace but complement PT:

  • When running, a VT is mounted on a PT
  • When a VT is blocked, its PT is unmounted (vs blocked) and becomes available to be re-mounted by any other ready-to-run VT
  • The association (mount/unmount) between VT and PT is ensured by a dedicated ForkJoinPool which plays the role of scheduler
  • By default, the number of PT in this pool equals the number of CPU cores
  • The number of PT used is very low even with millions of VT.

Internally, the JVM has been extensively redesigned around a new internal Continuation class, which allows a task to be suspended and restarted. Each Virtual Thread relies on a Continuation instance to implement the mount/unmount mechanisms.

Before going any further, it’s important to note that VTs will deliver maximum efficiency with IO-bound processing that often blocks. In contrast, CPU-bound processing will tend to monopolize the underlying PT without actually unmounted it. The direct use of PT therefore still makes sense for in-memory computation processing.

VT is part of Project Loom, which brings two complementary features: Structured Concurrency and Scoped Variables (both preview with Java 21).

Helidon and Virtual Threads

Helidon 3, which is production-ready and compliant with Java 17, is based on Netty, a reactive web server.

Version 4, still under development, introduces a technical shift with Nima, a new Web Server natively based on VT. This means you can code simply, in an imperative way, using Jakarta EE and MicroProfile standards, while running reactive.

Technical context

The examples below were developped in this technical context:

  • MacOS/ARM and Linux/Intel
  • OpenJDK Temurin 20.0.2
  • Maven 3.8.4
  • Helidon CLI 3.0.4
  • sdkman 5.18.2 (optional)
  • Helidon 3.2.2 and 4.0.0-M1

VT have been delivered in preview mode with Java 20. It is therefore necessary to explicitly authorize preview features in the Maven configuration and some commands. As we shall see, this will no longer be necessary with Java 21.

Installing Helidon CLI

First, let’s start by installing Helidon CLI to facilitate project initialization and development.

The easy way it to use sdkman:

sdk install helidon

An alternative is to install the Helidon CLI manually.

Platform Threads with Helidon 3

To start a Helidon project from scratch, run:

helidon init

Select:

  1. Helidon version: Helidon 3.2.2
  2. Flavor: Helidon MP
  3. Application Type: database
  4. JSON library: JSON-B
  5. JPA implementation: Hibernate
  6. Connection Pool: HikariCP
  7. Database server: H2
  8. Auto DDL: no
  9. Whatever suit you as Persistence Unit, Datasource, Maven and package names
  10. Do not enter the development loop (more this below).

A ready-to-package Maven project has been generated. This project exposes a REST API backed by a H2 database. The programming model is based on MicroProfile 5 and Jakarta EE 9.

Go to the project directory and run:

mvn package

An executable jar file with the name of your project has been created in the target directory. You can run it with the following command (dbdemo being the name of my project):

java -jar target/dbdemo.jar

You can check whether it is up and running with the curl commands described in the README.md.

Before moving further, let’s check which kind of thread is used by Helidon 3 to run REST API endpoints.

First off, change the Java version in pom.xml:

<properties>
  <maven.compiler.target>20</maven.compiler.target>
  <maven.compiler.source>20</maven.compiler.source>
</properties>

Configure the compilation plugin to enable preview features:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <compilerArgs>
      <compilerArg>--enable-preview</compilerArg>
    </compilerArgs>
  </configuration>
</plugin>

Add the following code to SimpleGreetResource.java:

@GET
@Path("/thread")
public String isVirtualThread() {
  return String.format("REST endpoint run by %s Thread",
    Thread.currentThread().isVirtual() ? "Virtual" : "Platform");
}

To quickly test this code, we’ll use the dev loop with Helidon CLI:

helidon dev --app-jvm-args "--enable-preview"

This command enables hot reload which helps shorten the development cycle. As mentioned above, the enable-preview option is needed with Java 20.

Let’s CURL the endpoint:

curl localhost:8080/simple-greet/thread

You should get the following answer:

REST endpoint run by Platform Thread

This simple test clearly demonstrates that Helidon 3 relies on classic Platform Threads to execute REST endpoints.

Virtual Thread with Helidon 4

So far, so good… But how to switch to Virtuall Threads? Let’s do it by moving our project to Helidon version 4.0.0-M1.

First off, change the Helidon version:

 <parent>
   <groupId>io.helidon.applications</groupId>
   <artifactId>helidon-mp</artifactId>
   <version>4.0.0-M1</version>
   <relativePath />
 </parent>

Then, update the jandex Maven groupId:

<dependency>
  <groupId>io.smallrye</groupId>
   <artifactId>jandex</artifactId>
   <scope>runtime</scope>
 </dependency>

Modify the Console Handler in logging.properties:

# Send messages to the console
handlers=io.helidon.logging.jul.HelidonConsoleHandler

Enter the dev loop:

helidon dev --app-jvm-args "--enable-preview"

Again, check the kind of thread:

curl localhost:8080/simple-greet/thread

You should now get the following answer:

REST endpoint run by Virtual Thread

Here we are: we have switched to Virtual Threads just by moving our project from Helidion 3 to 4!

Next steps

In this article, we have seen what Virtual Threads are, how they complement traditional Platform Threads and how to use them with Helidon.

Each framework has its own different strategy for adopting Virtual Threads. Helidon has taken an opiniated approach based on Nima, a Virtual Threads native web server. In contrast, Quarkus keeps using Platform Threads by default and requires the explicit use of @RunOnVirtualThread annotation on classes or methods.

This is a very basic starting point, in my next article, we will see how to monitor Virtual Threads.

References

Here are some videos you can watch to learn more about Java Loom and Helidon Nima.

José Paumard JEP Café:

The challenges of introducing Virtual Threads to the Java Platform by Alan Bateman

Virtual Threads power with Helidon Nima by Dmitry ALEKSANDROV

Helidon Nima and Virtual Threads with Thomas Langer

Comments

One response to “Running MicroProfile reactive with Helidon Nima and Virtual Threads”

  1. Monitoring Java Virtual Threads – Jean-François James Avatar

    […] my previous article, we’ve seen what Virtual Threads (VTs) are, how they differ from Platform Threads (PTs), and […]

    Like

Leave a comment