Virtual Thread Pinned By Synchronized Code

ID

java.virtual_thread_runs_synchronized_code

Severity

low

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Efficiency

Language

Java

Tags

efficiency

Description

Applies only to Java 21, 22 and 23. Since Java 24, virtual threads are no longer pinned by synchronized code, and this finding does not apply.

Reports Thread.startVirtualThread(task) or Thread.ofVirtual()…​.start(task) where task’s body contains a `synchronized block, or calls a method the project resolves as synchronized. A platform thread (new Thread(…​)) doing the same thing, and a task submitted to any other executor (including a custom ThreadPoolExecutor), are out of scope.

Rationale

On Java 21 through 23, code running inside a synchronized block or method pins the virtual thread to its underlying carrier OS thread for as long as the block is active: the thread cannot be unmounted even for a blocking operation inside it. That defeats the scalability benefit virtual threads exist to provide — many virtual threads sharing a small pool of OS threads — and can turn a blocking call inside a synchronized section into a bottleneck on the carrier pool.

Thread.startVirtualThread(() -> {
  synchronized (lock) {
    blockingCall(); // pins the carrier thread on Java 21-23
  }
});

Remediation

On an affected Java version, either replace the intrinsic lock with a java.util.concurrent lock (which does not pin), or run the task on a platform thread instead:

new Thread(() -> {
  synchronized (lock) {
    blockingCall();
  }
}).start();

© 2026 Xygeni. All rights reserved