Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage News JEP 425: Java Virtual Threads to Deliver Improved Throughput

JEP 425: Java Virtual Threads to Deliver Improved Throughput

This item in japanese

Lire ce contenu en français

JEP 425, Virtual Threads (Preview), has been promoted from Proposed to Target to Targeted status for JDK 19. This JEP, under the umbrella of Project Loom, introduces virtual threads. These lightweight threads aim to dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications to the Java platform. This is a preview feature.

Java was the first mainstream programming platform that took threads into the core language as its concurrent programming unit. The classic Java threads (an instance of java.lang.Thread) are a one-to-one wrapper over operating system (OS) threads (also known as platform threads). On the other hand, virtual threads are lightweight implementations of the Thread class that the JDK, rather than the OS, provides. Virtual threads use the platform thread to run their code, but it does not capture the OS thread for the code's entire lifetime. This enables many virtual threads to run their Java code on the same OS thread.

There are many benefits of a virtual thread. Most of all, it makes creating threads cheap and helps to increase application throughput. Server applications nowadays handle concurrent user requests by delegating each to an independent unit of execution, that is, a platform thread. This thread-per-request programming style is easy to understand, easy to program and easy to debug and profile. However, platform threads are limited in number because the JDK implements threads as wrappers around OS threads. So when an application needs to scale to increase throughput, it fails to do so with platform threads. Now, since a large number of virtual threads are easy to create, the thread-per-request programming style alleviates this scalability bottleneck with it.

Besides, when code running on a virtual thread calls a blocking I/O, the virtual thread gets automatically suspended until it can be resumed later. During this time, other virtual threads can take over the OS thread and continue to run their code. On the contrary, the OS thread would get blocked in a similar situation, which isn't desirable due to their limitations.

Virtual threads support thread-local variables, synchronization blocks, thread interruptions, etc. To Java developers, this means that virtual threads are simply threads that are cheap and plentiful, and the programming approach doesn't change at all. Existing Java code written targeting classic threads can easily run in a virtual thread without changes.

Developers who are keen to experiment with virtual threads can download the early access build of JDK 19 and become familiar with it. The Thread class provides a new method, startVirtualThread(Runnable), that takes an argument of type Runnable and creates virtual threads. Consider the following example:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        var vThread = Thread.startVirtualThread(() -> {
            System.out.println("Hello from the virtual thread");


Since this is a preview feature, a developer will need to provide the --enable-preview flag to compile this code as shown in the following command:

javac --release 19 --enable-preview

The same flag is also required to run the program:

java --enable-preview Main

However, one can directly run this using the source code launcher. In that case, the command line would be:

java --source 19 --enable-preview

The jshell option is also available, but requires enabling the preview feature as well:

jshell --enable-preview

While Thread.startVirtualThread(Runnable) is the convenient way to create a virtual thread, new APIs such as Thread.Builder, Thread.ofVirtual(), and Thread.ofPlatform() were added to create virtual and platform threads.

A virtual thread cannot be created using the public constructor. It runs as a daemon thread with NORM_PRIORITY. So, the Thread.setPriority(int) and Thread.setDaemon(boolean) methods cannot change a virtual thread's priority or turn it into a non-daemon thread. Also, the active threads carry virtual threads, so they can not be a part of any ThreadGroup. Usage of Thread.getThreadGroup() returns "VirtualThreads."

Virtual threads should never be pooled since each is intended to run only a single task over its lifetime. Instead, the model is the unconstrained creation of virtual threads. For this purpose, an unbounded executor has been added. It can be accessed via a new factory method Executors.newVirtualThreadPerTaskExecutor(). These methods enable migration and interoperability with existing code that uses thread pools and ExecutorService. Consider the following example:

import java.time.Duration;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 10_000).forEach(i -> {
                executor.submit(() -> {
                    return i;

The default scheduler for virtual threads is the work-stealing scheduler introduced in ForkJoinPool.

Developers interested in a deep dive into virtual threads and learning the backstory can listen to the InfoQ Podcast and YouTube session by Ron Pressler, consulting member of the technical staff at Oracle and project lead of Project Loom.

About the Author

Rate this Article