Java Virtual Threads (Project Loom) Cheat Sheet
Creating and managing virtual threads, structured concurrency, and pitfalls like pinning, for writing simple blocking-style concurrent Java code at scale.
2 PagesAdvancedFeb 10, 2026
Creating Virtual Threads
Virtual threads are cheap, JVM-scheduled threads — millions can run concurrently.
java
// Direct creationThread vt = Thread.ofVirtual().name("worker-1").start(() -> { System.out.println("running on " + Thread.currentThread());});vt.join();// Factory for use with ExecutorService-style codeThreadFactory factory = Thread.ofVirtual().name("worker-", 0).factory();// Unstarted, then start laterThread t = Thread.ofVirtual().unstarted(() -> doWork());t.start();
Virtual-Thread-Per-Task Executor
The idiomatic way to run many concurrent blocking tasks.
java
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { List<Future<String>> futures = new ArrayList<>(); for (int i = 0; i < 10_000; i++) { int id = i; futures.add(executor.submit(() -> fetch("https://example.com/" + id))); } for (var f : futures) { System.out.println(f.get()); }} // executor.close() waits for all tasks, one virtual thread per submitted task
Structured Concurrency
Scopes tie a task's lifetime to its subtasks — errors and cancellation propagate cleanly.
java
// java.util.concurrent.StructuredTaskScope (preview in recent JDKs)try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Subtask<String> user = scope.fork(() -> fetchUser(id)); Subtask<String> orders = scope.fork(() -> fetchOrders(id)); scope.join(); // wait for both, or fail fast on first exception scope.throwIfFailed(); // rethrow if any subtask failed return new Response(user.get(), orders.get());}
Pinning: What Blocks the Carrier Thread
Situations where a virtual thread pins its OS carrier thread, hurting scalability.
- synchronized blocks/methods- pins during the block; prefer java.util.concurrent.locks.ReentrantLock
- native calls (JNI)- pin for the duration of the native call
- Object.wait() inside synchronized- classic pinning source, migrate to Condition
- Thread.holdsLock queries- indicate you're in a pinning-prone region
- jdk.tracePinnedThreads- JFR/JDK flag to diagnose pinning at runtime
Pro Tip
Don't pool virtual threads and don't reuse them like platform threads — create a new one per task (they're designed to be cheap and disposable) and replace ReentrantLock/synchronized hotspots only where profiling shows real pinning, not preemptively.
Was this cheat sheet helpful?
Explore Topics
#JavaVirtualThreadsProjectLoom#JavaVirtualThreadsProjectLoomCheatSheet#Programming#Advanced#CreatingVirtualThreads#Virtual#Thread#Per#Concurrency#CheatSheet#SkillVeris