Under The Hood
backend java Java 21 (JEP 444, virtual threads stable)

Java Virtual Threads (Project Loom)

Last updated
Prerequisites:
Java threading basics
Understanding of blocking I/O and thread-per-request servers
  • java
  • virtual-threads
  • loom
  • concurrency
  • threading
  • jvm

Read at your depth

The practical view

Virtual threads let you write blocking-style code that scales like async code: Thread.ofVirtual().start(() -> { var r = client.get(url); process(r); }); or Executors.newVirtualThreadPerTaskExecutor(). Each virtual thread is a Java Thread instance, but it is scheduled onto a small pool of platform (carrier) threads by the JVM. When a virtual thread blocks on I/O, the JVM unmounts it from the carrier, runs other virtual threads on that carrier, and remounts it when the I/O completes. You keep normal try-with-resources, blocking calls, and stack traces — no CompletableFuture or reactive chains needed.

Legacy vs modern

Platform thread-per-request vs virtual thread-per-request

A platform thread per request is capped by OS resources; virtual threads make the thread count a heap concern, keeping blocking code readable.

before → after
Fixed platform thread pool
ExecutorService pool = Executors.newFixedThreadPool(200);
pool.submit(() -> handle(request));
Virtual thread per task
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
  pool.submit(() -> handle(request));
}

Interview gotchas

Context

The interviewer wants to verify that you know virtual threads are not free and understand the pinning failure mode introduced by Loom.

The mechanical answer

Pinning happens when a virtual thread, while holding a native or synchronized monitor, performs a blocking operation that the JVM cannot transparently unmount. Cases: synchronized blocks or methods during a blocking call (the monitor is tied to the carrier until released), native methods that block (JNI), and Object.wait in some paths. When pinned, the carrier cannot be reused for other virtual threads, so with many pinned threads you effectively run with as many carriers as pinned threads — the scaling benefit collapses. Java 24+ (JEP 491) removed most pinning for synchronized by making the monitor pin only when contended, but native pinning remains a documented limitation.

Trap

The naive answer is 'virtual threads never block the OS'. They do block carriers when pinned, and before JEP 491 synchronized was a common accidental cause (locks acquired around blocking I/O). Another trap: claiming pinning is a bug you can ignore — under heavy load it reproduces thread starvation symptoms. Naming the monitor-versus-native distinction and the JEP 491 change shows current knowledge.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.