← All articles
Backend1 min read

Virtual Threads in Java 21

Platform threads are expensive, so we pool them. Virtual threads are cheap enough that you can spawn millions and write plain blocking code again.

S

Swapnika Voora

Author

For years we wrapped blocking I/O in thread pools and reactive frameworks to avoid exhausting OS threads. Virtual threads flip that: they are managed by the JVM, cost almost nothing, and let you write straightforward blocking code.

Spawn freely

A virtual thread parks instead of blocking an OS thread when it hits I/O. That means one thread per task is suddenly a reasonable design.

Server.java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (var request : requests) {
        executor.submit(() -> {
            var response = httpClient.send(request);  // blocking is fine
            store(response);
        });
    }
}

Enable them in Spring Boot

One property switches Tomcat and the task executor to virtual threads.

application.properties
spring.threads.virtual.enabled=true

Takeaways

  • Use virtual threads for I/O-bound work, not CPU-bound number crunching.
  • Avoid pooling them — create one per task; they are disposable.
  • Watch for synchronized blocks around I/O, which can pin the carrier thread.

Reach for ReentrantLock instead of synchronized when guarding blocking sections, so virtual threads can unmount cleanly.

#java#concurrency#virtual-threads

More in Backend