The first time I hit "Too many threads" in a Spring Boot app, I did what most Java devs do — I threw in a thread pool, tuned the executor, and prayed it wouldn't crash in production.
For years, building scalable apps in Java meant juggling:
CompletableFuturegymnastic- Thread pool sizing voodoo
- Reactive frameworks (WebFlux, Reactor) that scared half the team
Then came Project Loom.
And suddenly, writing concurrent code in Spring Boot feels… normal again.

What Are Virtual Threads?
Virtual threads are a new feature in the JDK (stable in Java 21) that lets you create thousands (even millions) of lightweight threads without the memory and scheduling cost of classic OS threads.
Think of them as:
- Cheaper threads → 1MB stack? Nope, just a few KB.
- Smarter scheduling → Java manages them, not the OS.
- Blocking that doesn't hurt → calling
Thread.sleep()or waiting on I/O doesn't block the underlying carrier thread.
Why This Matters for Spring Boot
Spring Boot apps are full of blocking calls:
- JDBC queries
- REST calls to other services
- File I/O
- Message queues
In the old model, every blocking call wasted a precious OS thread. With Loom, each of those operations simply parks the virtual thread until the result is ready — without burning CPU or memory.
From Reactive to "Normal" Code
Before Loom, if you wanted to handle 10k concurrent requests, you had two choices:
- Huge thread pools → memory-hungry, hard to tune
- Reactive stack (WebFlux) → powerful but forces you into callbacks,
Mono,Flux, and functional spaghetti
With Loom, you can write plain imperative code again:
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}No CompletableFuture, no subscribe(), no async chains — just normal Java methods that scale because they run on virtual threads.
How To Enable Virtual Threads in Spring Boot
Spring 6 / Spring Boot 3 make it easy. Just define a TaskExecutor backed by Loom:
@Bean
TaskExecutor applicationTaskExecutor() {
return new SimpleAsyncTaskExecutor(r -> Thread.ofVirtual().factory().newThread(r));
}Or for web apps (Tomcat, Jetty, Undertow), you can configure the server to run requests on virtual threads with a single property in Spring Boot 3.2+:
spring.threads.virtual.enabled=trueThat's it. Your controller methods now run on virtual threads.
The Benefits You'll Notice
Less Boilerplate → No need for Reactor if you don't want it Huge Concurrency → 10k+ requests without tuning pools Simpler Debugging → Stack traces look like normal Java again Future-Proof → Loom is part of the JDK, not another library to upgrade
When Not to Use Loom (Yet)
- If you're on Java 17 or lower → upgrade first (LTS in Java 21).
- If you rely heavily on libraries that aren't Loom-aware (rare but possible).
- If you already invested heavily in WebFlux and love reactive pipelines.
The Takeaway
For years, Java developers had to choose between "easy to write" (blocking code) and "scalable" (async/reactive).
With Project Loom, you don't have to choose anymore.
Your Spring Boot apps can handle massive concurrency — while you keep writing simple, imperative Java code.
The future of Spring Boot isn't about learning another async framework. It's about letting Loom do the heavy lifting while you write code that looks like it always should have.