Virtual Threads Migration Guide (Beginner
Friendly)
Scenario 1: Tomcat / Web Server Thread Pool
Before: Fixed thread pools (e.g., 200 threads). Extra requests were queued or rejected.
Problem: Memory heavy (~1MB per thread). Blocking I/O wastes threads.
After: Each request gets its own Virtual Thread (~2KB each).
Example: 1000 concurrent requests → platform threads use ~1GB memory. Virtual threads use
only few MB.
Benefit: No thread pool tuning, better scalability, higher throughput under I/O waits.
Scenario 2: @Async Task Executor
Before: Needed careful tuning of corePoolSize, maxPoolSize, queueCapacity.
Problem: Burst traffic caused RejectedExecutionException.
After: [Link]() — no tuning required.
Example: 1000 emails taking 500ms each.
Platform threads (50 max): ~10 seconds total.
Virtual threads: all run simultaneously → ~500ms total.
Benefit: Faster burst handling, no rejection, blocking is safe.
Scenario 3: @Scheduled Tasks
Before: Long tasks blocked scheduler threads.
Problem: Shared pool caused starvation.
After: Each scheduled execution runs in its own Virtual Thread.
Example: Report job runs every 60s and takes 45s.
Virtual threads allow overlapping safely without blocking others.
Benefit: No starvation, safe DB/HTTP blocking.
Scenario 4: RestTemplate (Blocking HTTP)
Before: Required CompletableFuture and async wrappers to avoid blocking.
After: Write simple blocking code inside Virtual Threads.
Example: Call 3 APIs sequentially in clean readable code.
Benefit: Cleaner code, better debugging, same scalability.
Scenario 5: JDBC / Database Calls
Before: Thread pool size had to match DB connection pool.
Problem: Threads blocked waiting for DB connections.
After: Virtual threads park while waiting for connection.
Example: 500 requests, 20 DB connections.
Platform threads: 180 blocked doing nothing.
Virtual threads: 480 parked, carrier threads free.
Benefit: Decoupled pool sizing, better resource utilization.
Scenario 6: General ThreadPoolTaskExecutor
Before: Constant operational tuning and monitoring.
After: Virtual thread per task executor.
Benefit: No rejection policies, no queue monitoring, simpler operations.
Scenario 7: Spring Security Context Propagation
Before: SecurityContext lost in async threads.
After: DelegatingSecurityContextExecutor with Virtual Threads.
Benefit: Correct authentication propagation, no silent bugs.
Scenario 8: Legacy XML Config
Before: Multiple thread pools competing for platform threads.
After: Virtual threads remove need for many tuned pools.
Benefit: Reduced config, predictable behavior.
Scenario 9: Observability
Before: Large thread dumps hard to analyze.
After: Named Virtual Threads + JFR support.
Benefit: Easier debugging, pinning detection, clearer performance insights.
Overall Key Takeaway
Virtual Threads allow writing simple blocking code with high scalability.
They remove most thread pool tuning and operational complexity.
Best suited for I/O heavy applications (HTTP, DB, Messaging).
Beginner Tip: If your app blocks on I/O, Virtual Threads are a major win.