100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace

Java Virtual Threads (Project Loom) Cheat Sheet

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