Java's Project Loom promised a "free lunch": swap Executors.newFixedThreadPool() for Executors.newVirtualThreadPerTaskExecutor() and watch your throughput fly. It's a great pitch and it's also only half the story. The other half only shows up once you stop simulating I/O-bound work and start hitting the CPU.
I wanted a benchmark that felt closer to something I'd actually build, so instead of a toy "sleep and print" example, I wrote a T+1 stock settlement simulator: 8,000 trades that need to clear, plus a Monte Carlo Value-at-Risk (VaR) calculation running 20,000 simulations per trade. One workload is I/O-bound (waiting on a settlement clearinghouse call), the other is pure CPU. Running both side by side on the same machine made the boundary between "virtual threads help" and "virtual threads don't help" impossible to miss.
Part 1 — I/O-Bound Work: Settling 8,000 Trades
The setup: each trade settlement blocks on a simulated I/O call (think: a REST call to a custodian or a clearinghouse). I ran it three ways.
Strategy







