Recently I explored virtual threads running Helidon 4.0.0-M1 on Java 20. In this article I will share some things I learned along the way and an example project. I plan to go more in depth in future posts.

Virtual Threads

"Virtual threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications."
...virtual threads can significantly improve application throughput when the number of concurrent tasks is high (more than a few thousand), and the workload is not CPU-bound, since having many more threads than processor cores cannot improve throughput in that case.

Virtual threads:

  • increase throughput, not speed
  • are provided by the JDK (not the OS)
  • share OS threads - M:N
    • vs. a platform thread that "captures" an OS thread - 1:1
    • vs. outdated "green threads" - M:1
  • release ("unmount") the OS carrier thread when blocked*
    • except when:
      • executing code in a synchronized block or method
      • executing native method or foreign function
    • a JDK Flight Recorder (JFR) event is emitted when thread blocks when pinned
    • can trigger a stack trace when thread blocks while pinned via: jdk.tracePinnedThreads=full|short
  • are an instance of java.lang.Thread
    • support thread local variables
    • support thread interruption
  • are usually short lived
  • are debuggable
    • but dump differently (say wat?) bc scale
      • possibly millions of virtual threads
  • are scheduled differently
    • scheduled by JVM not the OS
    • the JVM uses a work-stealing ForkJoinPool of platform threads (not the common pool)
    • the number of platform threads in this pool defaults to the number of processors
      • can be tuned with system properties: jdk.virtualThreadScheduler.parallelism and jdk.virtualThreadScheduler.maxPoolSize
  • have no visibility to their "carrier" platform thread when executing
  • stacks are stored in the heap as "stack chunk" objects
    • stack chunks are not GC roots
    • stack chunks can not be humongous
  • should never be pooled

Virtual threads were first introduced with Java 17. The current version (as of this writing) of Java is version 20 and virtual threads are still a "preview" feature. However, Java 21, an LTS (long term support), is just around the corner with a GA release targeted for 9/19/2023.


Helidon is a cloud-native, open‑source set of Java libraries for writing microservices that run on a fast web core powered by Netty.* (well not exactly Netty)
Helidon Níma is the first Java microservices framework based on virtual threads.

When I began looking into frameworks that support virtual threads, I first checked whether Undertow supported virtual threads. In a past life, I built a private framework based on Undertow and Guice and I still feel pretty good about this approach. But, Undertow… nope.

I then looked into frameworks like Micronaut and Quarkus. Both have a lot to offer off the click. However, I really like control and I wish that the config and the DI capabilities could be dynamically configured per preference. Both of these frameworks adopted a DI interface (eg. javax.inject or jakarta.inject) yet prevent or limit configurability.

IMO, Helidon is a lightweight, embeddable framework and, hey!, it supports virtual threads (v4.0.0-M1).

How To

Code speaks louder than words.

Random Jots

Local Tools

> gradle -v
Gradle 8.2.1
Build time: 2023-07-10 12:12:35 UTC
Revision: a38ec64d3c4612da9083cc506a1ccb212afeecaa
Kotlin: 1.8.20
Groovy: 3.0.17
Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM: 20.0.1 (Eclipse Adoptium 20.0.1+9)
OS: Mac OS X 13.4 x86_64

How to Local

> git clone
> cd pub/helidon4M1jdk20
> gradle clean build
> java --enable-preview -jar app/build/libs/app-all.jar
Aug 16, 2023 3:31:31 PM io.helidon.common.features.HelidonFeatures features
INFO: Helidon NIMA 4.0.0-M1 features: [WebServer]
Aug 16, 2023 3:31:31 PM io.helidon.nima.webserver.ServerListener start
INFO: [0x51fadaff] bound for socket '@default'
Aug 16, 2023 3:31:31 PM io.helidon.nima.webserver.LoomServer startIt
INFO: Started all channels in 46 milliseconds. 1093 milliseconds since JVM startup. Java 20.0.1+9
> curl 'http://localhost:9710/greeting'

Print value of Thread.currentThread()

VirtualThread[#31,[0x38234a38 0x33cc5fd8] Nima socket]/runnable@ForkJoinPool-1-worker-1

Helidon 4M1, Java 20, and Virtual Threads Pt. 1 - Getting Started

Part 1 - Random thoughts along my adventure using Java 20 virtual threads on Helidon 4.0.0-M1.