← Back to Advanced Java Course | Chapter 4: Modern Java Features | Lesson 9 of 12

Java Virtual Threads

Virtual threads, finalized in Java 21 via Project Loom, are lightweight JVM-managed threads that let a program run huge numbers of blocking-style tasks cheaply and concurrently.
Syntax
java
Thread.ofVirtual().start(runnable);

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(task);
}

What are Virtual Threads?

Virtual threads, finalized in Java 21 as part of Project Loom, are lightweight threads managed entirely by the JVM rather than mapped one-to-one to an operating system thread, allowing a program to run huge numbers of them cheaply.

Example: What are Virtual Threads?

java
public class Main {
	public static void main(String[] args) throws InterruptedException {
		Thread vt = Thread.ofVirtual().start(() -> System.out.println("Running on a virtual thread")); // JVM-managed, lightweight
		vt.join();
	}
}

Creating a Virtual Thread

Virtual threads are created using the new Thread.ofVirtual() builder or the shortcut Thread.startVirtualThread, both of which behave like a normal Thread from the calling code's perspective but carry far less overhead.

Example: Creating a Virtual Thread

java
public class Main {
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = Thread.ofVirtual().start(() -> System.out.println("via ofVirtual()"));
		Thread t2 = Thread.startVirtualThread(() -> System.out.println("via startVirtualThread")); // shortcut
		t1.join(); t2.join();
	}
}

Virtual Threads vs Platform Threads

A platform thread, created with new Thread(), maps directly to a limited operating system thread, while a virtual thread is scheduled by the JVM onto a small pool of carrier threads, letting millions of virtual threads exist where only thousands of platform threads could.

Example: Virtual Threads vs Platform Threads

java
// Define the class Main
public class Main {
	// Program entry point: the JVM starts running here
	public static void main(String[] args) throws InterruptedException {
		// Create a new Thread object and store it in platform
		Thread platform = new Thread(() -> System.out.println("Platform thread: " + Thread.currentThread()));
		Thread virtual = Thread.ofVirtual().unstarted(() -> System.out.println("Virtual thread: " + Thread.currentThread()));
		// Start the thread; it runs in parallel with this code
		platform.start(); platform.join();
		// Start the thread; it runs in parallel with this code
		virtual.start(); virtual.join();
	}
}

Virtual Threads with ExecutorService

Executors.newVirtualThreadPerTaskExecutor() creates a new virtual thread for every task submitted to it, rather than reusing a fixed-size pool, which works well because virtual threads are cheap enough to create one per task.

Example: Virtual Threads with ExecutorService

java
// Import java.util.concurrent.* so it can be used by its short name
import java.util.concurrent.*;
// Define the class Main
public class Main {
	// Program entry point: the JVM starts running here
	public static void main(String[] args) throws Exception {
		// Try code that might throw an exception
		try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
			executor.submit(() -> System.out.println("New virtual thread per task"));
		}
	}
}

When to Use Virtual Threads

Virtual threads are ideal for high-throughput, I/O-bound workloads like handling many concurrent network requests or database calls, since blocking a virtual thread is inexpensive, but they offer little benefit for CPU-bound computation, which is limited by processor cores instead of thread count.

Example: When to Use Virtual Threads

java
// Define the class Main
public class Main {
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Print a line to the console
		System.out.println("Good fit: many I/O-bound network/database calls");
		// Print a line to the console
		System.out.println("Poor fit: CPU-bound computation, limited by processor cores");
	}
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Expecting the program to wait for virtual threads, when they are daemon threads, so main can exit first unless you call join().
  2. Putting virtual threads in a fixed-size pool, when they are cheap and meant to be created one per task.
  3. Using virtual threads for CPU-heavy work, when they help with blocking I/O and give no speed-up for tasks that keep the CPU busy.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.