The Java Virtual Thread Pinning Trap That Turns Your Loom Migration Into a Performance Regression | by Illya Yalovoy | Jun, 2026 | MediumSitemapOpen in appSign up<br>Sign in
Medium Logo
Get app<br>Write
Search
Sign up<br>Sign in
Member-only story
The Java Virtual Thread Pinning Trap That Turns Your Loom Migration Into a Performance Regression
Illya Yalovoy
15 min read·<br>Just now
Listen
Share
Why your synchronized blocks silently throttle 100,000 virtual threads down to 8, and the mechanical fixes that actually restore throughput<br>The Migration That Made Things Worse<br>Your team spent two sprints migrating to virtual threads. The PR was clean — swap the executor, remove the thread pool sizing, delete the bulkhead configs. You deploy to staging, run your load test, and watch throughput drop by 40%. CPU utilization sits at 8%. Your carrier threads are pegged at 100%. The service is doing less work while appearing more busy. This is not a bug in your code in the traditional sense. This is pinning, and it just turned your Loom migration into a performance regression.<br>I hit this exact scenario on a service that handles DynamoDB calls through the AWS SDK’s HTTP connection pool. The pool internally uses synchronized blocks to manage connection checkout and return. Under low concurrency, everything looked fine. Under production load with a few hundred concurrent requests, virtual threads stacked up waiting for carrier threads that were pinned inside those synchronized regions. Effective concurrency collapsed from “unlimited virtual threads” to exactly the number of carrier threads, which on our ECS tasks meant 4. Four concurrent DynamoDB calls. On a service that previously ran 200 platform threads without issue. Our p99 latency went from 45ms to over 1.2 seconds.
Written by Illya Yalovoy<br>9 followers<br>·8 following
Help
Status
About
Careers
Press
Blog
Store
Privacy
Rules
Terms
Text to speech