This article dives into a subtle but tricky detail of Java’s virtual threads: something called “pinning”. At first glance it looks like an internal implementation detail, but in practice it can freeze an entire application. The core idea starts simple. Virtual threads were created to solve an old Java problem, which is the high cost of OS threads. Instead of creating thousands of heavy threads, Java creates lightweight ones and reuses a small number of real threads underneath. This works really well when a thread blocks on I/O, because it releases the underlying thread so another task can use it. The problem starts when that doesn’t happen. Pinning is exactly when a virtual thread gets stuck to a real thread. This usually happens when you use things like `synchronized` or native calls. In those situations, the virtual thread can’t detach from the underlying thread. It ends up holding onto it just like a traditional thread would. Now comes the critical part. If multiple virtual threads get pinned at the same time, you can end up consuming all available real threads. At that point, the system hits a kind of standstill. There are no threads left to run the remaining work. In practice, it looks like a deadlock, even if it’s not the classic lock cycle scenario. The article shows that this isn’t just theoretical. It has already happened in real systems. A typical scenario looks like this: multiple requests come in, each one enters a synchronized block, blocks somewhere along the way, and ends up pinning the underlying thread. Before you notice, all threads in the pool are occupied and the server stops responding. One of the most interesting takeaways is how this breaks a common assumption. A lot of people think “virtual threads = infinite scalability”. That’s not really true. They scale a lot, but they still depend on a limited number of real threads underneath. If you block those threads the wrong way, you’re in trouble. The article’s point is not that virtual threads are flawed, but that traditional synchronization doesn’t play well with them. Old code that relies on `synchronized` can become a trap in this new model. In the end, the message is pretty practical. Virtual threads are powerful, but not magical. If you mix them carelessly with traditional locking, you can create serious bottlenecks or even freeze your system. The implicit advice is to avoid `synchronized` in critical paths and prefer more modern alternatives that don’t pin threads. If you want, I can walk you through how to detect this in production or how to avoid it in real code. source: https://shbhmrzd.github.io/java/concurrency/virtual-threads/2026/04/25/java-virtual-threads-pinning-and-the-deadlock-problem.html

