← 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.

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
public class Main {
	public static void main(String[] args) throws InterruptedException {
		Thread platform = new Thread(() -> System.out.println("Platform thread: " + Thread.currentThread()));
		Thread virtual = Thread.ofVirtual().unstarted(() -> System.out.println("Virtual thread: " + Thread.currentThread()));
		platform.start(); platform.join();
		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.*;
public class Main {
	public static void main(String[] args) throws 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
public class Main {
	public static void main(String[] args) {
		System.out.println("Good fit: many I/O-bound network/database calls");
		System.out.println("Poor fit: CPU-bound computation, limited by processor cores");
	}
}

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.