When should you use virtual threads, and when should you… — Cracked Java
// Concurrency & Multithreading · Virtual Threads & Structured Concurrency (Loom)
SeniorTheoryBig TechAmazon

When should you use virtual threads, and when should you not?

Use virtual threads for high-concurrency, blocking I/O workloads written in thread-per-task style — servers handling many requests that each spend most of their time waiting on sockets, databases, or downstream services. Do not use them for CPU-bound work, and never pool them.

Where they win

The sweet spot is the classic thread-per-request server with lots of in-flight, mostly-waiting requests: REST endpoints fanning out to other services, database calls, message consumers. Each request gets its own virtual thread, writes simple sequential blocking code, and unmounts while waiting — so a handful of carriers serve thousands of concurrent requests. You get reactive-level throughput with imperative-level readability and stack traces.

// Thread-per-request: one virtual thread per task, no pool reuse
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
    for (Request r : incoming) {
        exec.submit(() -> {
            var user = userService.fetch(r.userId());   // blocks -> unmounts
            var orders = orderService.fetch(r.userId()); // blocks -> unmounts
            respond(r, render(user, orders));
        });
    }
}

Where they do not help

  • CPU-bound work (encoding, hashing, number crunching): a virtual thread holds its carrier the whole time it computes — there is nothing to unmount on. You gain nothing over a platform-thread pool sized to the core count, and you add scheduling overhead. Use a fixed pool or a parallel stream.
  • Code dominated by synchronized around I/O or native blocking: pinning negates the benefit (less so after JEP 491 in Java 24, but native frames still pin).

Anti-patterns

Other cautions: heavy ThreadLocal use multiplied across millions of threads can blow up memory (prefer ScopedValue); and migrating a fixed pool to virtual threads can suddenly overwhelm a downstream that the pool was implicitly rate-limiting.

Rule of thumb

If a task spends most of its life blocked waiting, virtual threads scale it almost for free. If it spends most of its life computing, they do not — bound that work to the number of CPUs.

Mark your status