Java has always been a reliable and powerful programming language for building robust and scalable applications. With each release, Java introduces new features and enhancements that further empower developers to tackle complex challenges. Java 19, the latest iteration of the language, brings a groundbreaking feature called “Virtual Threads.” In this blog post, we’ll delve into the world of Java 19 Virtual Threads, understand their significance, and explore examples to showcase their potential.

Table of Contents
What are Virtual Threads?
Virtual Threads, also known as Fibers, are lightweight, user-space threads that can be created and managed by the Java Virtual Machine (JVM) without relying on operating system threads. Unlike traditional threads, Virtual Threads are much lighter in terms of memory consumption and can scale efficiently to handle massive concurrency.
Virtual threads are implemented using a technique called M:N scheduling. This means that a single platform thread can execute multiple virtual threads at the same time. The JVM scheduler dynamically assigns virtual threads to platform threads, based on the availability of resources and the needs of the application.
This approach has several advantages over traditional platform threads. First, it allows applications to create a much larger number of threads without running out of resources. Second, it can improve the throughput of applications that spend a lot of time waiting for I/O operations. Third, it can reduce the overhead of context switching between threads.
Virtual threads are still a preview feature in Java 19, but they are expected to become a standard part of the language in future releases. If you are developing applications that need to handle a large number of concurrent tasks, then you should consider using virtual threads.
Here are some examples of applications that could benefit from using virtual threads:
- Web servers
- Database servers
- Message brokers
- High-performance computing applications
To use virtual threads in Java 19, you need to enable the -enable-preview
flag when you start the JVM. You can then create virtual threads using the java.lang.VirtualThread
class.
Creating a Java virtual thread is now possible using the new APIs: Thread.ofVirtual() and Thread.ofPlatform(). The traditional Thread.getId() method has been abandoned in JDK 19. To illustrate the usage, consider the following examples:
Example 1: Creating and starting a virtual thread
Runnable runnable = () -> System.out.println(Thread.currentThread().threadId());
Thread thread = Thread.ofVirtual().name("testVT").unstarted(runnable);
Thread testPT = Thread.ofPlatform().name("testPT").unstarted(runnable);
testPT.start();
In this example, we create a virtual thread named “testVT” and a platform thread named “testPT” using the Thread.ofVirtual() and Thread.ofPlatform() APIs, respectively. The threads are not started yet.
Example 2: Quick creation and start of a virtual thread
Runnable runnable = () -> System.out.println(Thread.currentThread().threadId());
Thread thread = Thread.startVirtualThread(runnable);
Here, we use the Thread.startVirtualThread(Runnable) method to quickly create and start a virtual thread with the given runnable task.
Example 3: Determining if a thread is virtual
Runnable runnable = () -> System.out.println(Thread.currentThread().isVirtual());
Thread thread = Thread.startVirtualThread(runnable);
In this example, we check if the current thread is a virtual thread by calling Thread.isVirtual().
Example 4: Waiting and sleeping with virtual threads
Runnable runnable = () -> System.out.println(Thread.sleep(10));
Thread thread = Thread.startVirtualThread(runnable);
thread.join();
Here, we make the virtual thread sleep for 10 milliseconds using Thread.sleep(10). The thread.join() call waits for the virtual thread to finish execution.
Example 5: Creating an ExecutorService with virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("hello"));
}
In this example, we create an ExecutorService using Executors.newVirtualThreadPerTaskExecutor(), which creates a new virtual thread for each task submitted to the executor. The “hello” task is submitted for execution.
Java 19 Virtual Threads provide compatibility and migration options for existing code that uses thread pools and ExecutorService. These advancements open up new possibilities for efficient concurrency management and better utilization of system resources.
Virtual threads support all of the same features as platform threads, including thread-local variables, synchronized blocks, and thread interruption. This means that you can use virtual threads in your existing Java code without making any changes.
Virtual threads are a powerful new feature that can help you to improve the performance and scalability of your Java applications. If you are developing applications that need to handle a large number of concurrent tasks, then you should consider using virtual threads.

What is the limit of virtual threads in Java?
The JVM maintains a pool of platform threads, created and maintained by a dedicated ForkJoinPool. Initially, the number of platform threads equals the number of CPU cores, and it cannot increase more than 256.
The number of virtual threads that can be created is limited by the number of platform threads available. In other words, you cannot create more virtual threads than there are platform threads.
What is the max JVM threads?
Each JVM server can have a maximum of 256 threads to run Java applications. This limit can be configured using the -Xmx
and -Xms
flags.
The -Xmx
flag specifies the maximum amount of memory that the JVM can use. The -Xms
flag specifies the initial amount of memory that the JVM will use.
If you need to run Java applications with more than 256 threads, then you can use a clustered deployment. In a clustered deployment, multiple JVM servers are used to run the application. This allows you to scale the application horizontally by adding more JVM servers.
I hope this blog post has given you a better understanding of Java 19 virtual threads. If you have any questions, please feel free to leave a comment below.
Significance of Virtual Threads:
- Reduced Resource Consumption: Virtual Threads consume significantly less memory compared to traditional threads. They can be created in large numbers, enabling efficient scaling and handling of concurrent tasks without straining system resources.
- Enhanced Concurrency: By leveraging Virtual Threads, developers can easily achieve high levels of concurrency in their applications. Virtual Threads are designed to handle millions of lightweight threads, making it feasible to write highly concurrent code without the need for complex thread pool management.
- Simplified Asynchronous Programming: Virtual Threads simplify the implementation of asynchronous programming patterns. Developers can write code that looks like synchronous code, allowing them to reason about program behavior more easily and avoid pitfalls associated with traditional callback-based asynchronous programming.

Examples:
Let’s explore a few examples to understand how Virtual Threads can be leveraged to improve concurrency and simplify asynchronous programming.
Example 1:
Concurrent Web Requests Consider a scenario where an application needs to make multiple web requests concurrently. With Virtual Threads, achieving high concurrency becomes straightforward:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class VirtualThreadsExample {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request1 = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/endpoint1"))
.build();
HttpRequest request2 = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/endpoint2"))
.build();
// Execute requests concurrently using Virtual Threads
CompletableFuture<HttpResponse<String>> response1 = client.sendAsync(request1, BodyHandlers.ofString());
CompletableFuture<HttpResponse<String>> response2 = client.sendAsync(request2, BodyHandlers.ofString());
// Process responses when they are complete
response1.thenAccept(r -> System.out.println("Response from endpoint1: " + r.body()));
response2.thenAccept(r -> System.out.println("Response from endpoint2: " + r.body()));
}
}
In this example, we create two virtual threads to make concurrent web requests using the HttpClient
API. The responses are processed asynchronously when they become available.

Example 2:
Simplified Asynchronous Code Virtual Threads enable the use of familiar control flow constructs, making asynchronous code more intuitive:
import java.util.concurrent.CompletableFuture;
public class VirtualThreadsExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, ", virtualThreadExecutor())
.thenApplyAsync(result -> result + "Virtual Threads!", virtualThreadExecutor())
.thenApplyAsync(String::toUpperCase, virtualThreadExecutor());
future.thenAccept(System.out::println);
}
private static Executor virtualThreadExecutor() {
return Executors.newVirtualThreadExecutor();
}
}
Here, we create a CompletableFuture
chain using Virtual Threads. Each step in the chain is executed asynchronously, with the final result printed when it becomes available.
Conclusion:
Java 19 Virtual Threads open up new possibilities for achieving high concurrency and simplifying asynchronous programming. By leveraging lightweight user-space threads, developers can write scalable and efficient code without the complexity of managing traditional threads. With reduced resource consumption and improved concurrency, Virtual Threads are set to revolutionize concurrent programming in Java. As Java continues to evolve, Virtual Threads prove to be a powerful addition to the language, empowering developers to build even more robust and responsive applications.
Dive into this insightful post on CodingReflex to unlock the power of Quarkus, Java’s revolutionary framework for building ultra-speed applications.