<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/virtual-threads-after-jdk-24-what-changed-for-production-java-1iad53gy7" -->

---
title: Virtual Threads After JDK 24: What Changed for...
description: JDK 24&#x27;s JEP 491 eliminated the monitor-related carrier-thread pinning that caused production stalls (like Netflix&#x27;s Java 21 incident) when virtual threads...
canonical: https://daily.dev/posts/virtual-threads-after-jdk-24-what-changed-for-production-java-1iad53gy7
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Virtual Threads After JDK 24: What Changed for Production Java | daily.dev
og:description: JDK 24&#x27;s JEP 491 eliminated the monitor-related carrier-thread pinning that caused production stalls (like Netflix&#x27;s Java 21 incident) when virtual threads...
og:url: https://daily.dev/posts/virtual-threads-after-jdk-24-what-changed-for-production-java-1iad53gy7
og:image: https://api.daily.dev/og/posts/1IaD53GY7.png
og:image:alt: Virtual Threads After JDK 24: What Changed for Production Java
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Virtual Threads After JDK 24: What Changed for Production Java

**[InfoQ](https://daily.dev/sources/infoq)** · 28 min read · 0 upvotes · 0 comments

## Summary

JDK 24's JEP 491 eliminated the monitor-related carrier-thread pinning that caused production stalls (like Netflix's Java 21 incident) when virtual threads blocked inside synchronized code. With JDK 25 LTS, the primary risk has shifted to downstream-resource exhaustion: removing the Tomcat thread-pool ceiling exposes connection pools, rate limits, and file descriptors as the new bottlenecks. Key failure modes include ThreadLocal caches silently reinitializing on every request (2,216x more allocations vs. platform threads in benchmarks), and InheritableThreadLocal context disappearing in StructuredTaskScope child threads. The recommended fixes are: use Semaphores for explicit resource bounding at every shared resource, migrate request context to finalized Scoped Values (JEP 506, JDK 25), audit ThreadLocal usage, and monitor with JFR's jdk.VirtualThreadPinned event. Spring MVC plus virtual threads is the right default for blocking I/O services; Spring WebFlux remains necessary for streaming, SSE, WebSockets, and backpressure-sensitive workloads. Structured Concurrency is still in preview and not yet stable for public APIs.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.infoq.com/articles/virtual-threads-after-jdk24>

## Questions this post answers

### Why does my ThreadLocal cache stop working after switching to virtual threads in Java?

ThreadLocal-based caching silently stops caching under virtual threads because each virtual thread is short-lived and not reused across requests, so a fresh ThreadLocalMap is created per task. In a benchmark, a ThreadLocal.withInitial() cache for SimpleDateFormat initialized 200 times under platform threads but 443,267 times under virtual threads for the same workload, a 2,216x increase that manifests as unexplained GC pressure rather than an exception.

_Developers debugging unexplained GC pressure after enabling virtual threads can dig into ThreadLocal pitfalls like this on daily.dev._

### Does JDK 24 fix virtual thread pinning on synchronized blocks?

Yes, JEP 491 in JDK 24 removes monitor-related carrier-thread pinning by tracking monitor ownership by virtual thread identity instead of carrier identity, letting virtual threads unmount inside synchronized blocks and reacquire monitors after waking. This resolved the stall class Netflix documented on Java 21. Residual pinning still occurs for native method frames, class loading, and local file I/O on Linux, tracked via the JFR jdk.VirtualThreadPinned event.

_Teams planning a JDK upgrade to unblock virtual thread rollouts can track fixes like JEP 491 through daily.dev._

### Should I migrate from Spring WebFlux to Spring MVC with virtual threads on JDK 25?

It depends on why WebFlux was adopted: services that chose it only to avoid blocking I/O at scale have a clear case to migrate, since blocking JDBC and JPA queries now scale acceptably on virtual threads with simpler code and readable stack traces. Services needing streaming, backpressure, server-sent events, or WebSockets should keep WebFlux, since virtual threads do not add backpressure semantics that Reactor provides.

_Engineers weighing Spring MVC versus WebFlux for their service architecture can compare trade-offs like these on daily.dev._

## Similar posts on daily.dev

- [Your Loom App Quietly Became a Thread Pool Again: A Field Guide to Virtual Thread Pinning](https://daily.dev/posts/your-loom-app-quietly-became-a-thread-pool-again-a-field-guide-to-virtual-thread-pinning-sbxl9qgff) · Foojay.io · 1 upvotes · 0 comments
- [Synchronize Virtual Thread Without Pinning](https://daily.dev/posts/synchronize-virtual-thread-without-pinning-xujbj1bjy) · Baeldung · 7 upvotes · 0 comments

---

Tags: [#java](https://daily.dev/tags/java), [#spring-boot](https://daily.dev/tags/spring-boot)

[View this post on daily.dev](https://daily.dev/posts/virtual-threads-after-jdk-24-what-changed-for-production-java-1iad53gy7)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Virtual Threads After JDK 24: What Changed for Production Java","url":"https://daily.dev/posts/virtual-threads-after-jdk-24-what-changed-for-production-java-1iad53gy7","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/virtual-threads-after-jdk-24-what-changed-for-production-java-1iad53gy7"},"datePublished":"2026-07-31T09:05:42.599Z","dateModified":"2026-09-13T21:32:42.958Z","description":"JDK 24's JEP 491 eliminated the monitor-related carrier-thread pinning that caused production stalls (like Netflix's Java 21 incident) when virtual threads...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/fe173a1a862403bd8eed2334594d064c?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/fe173a1a862403bd8eed2334594d064c?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"InfoQ","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"InfoQ","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/afc3bced3e1e4b188dd9127017a60e0c","url":"https://daily.dev/sources/infoq"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/virtual-threads-after-jdk-24-what-changed-for-production-java-1iad53gy7","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"java,spring-boot","timeRequired":"PT28M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"InfoQ","item":"https://daily.dev/sources/infoq"},{"@type":"ListItem","position":3,"name":"Virtual Threads After JDK 24: What Changed for Production Java"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/virtual-threads-after-jdk-24-what-changed-for-production-java-1iad53gy7#faq","mainEntity":[{"@type":"Question","name":"Why does my ThreadLocal cache stop working after switching to virtual threads in Java?","acceptedAnswer":{"@type":"Answer","text":"ThreadLocal-based caching silently stops caching under virtual threads because each virtual thread is short-lived and not reused across requests, so a fresh ThreadLocalMap is created per task. In a benchmark, a ThreadLocal.withInitial() cache for SimpleDateFormat initialized 200 times under platform threads but 443,267 times under virtual threads for the same workload, a 2,216x increase that manifests as unexplained GC pressure rather than an exception. Developers debugging unexplained GC pressure after enabling virtual threads can dig into ThreadLocal pitfalls like this on daily.dev."}},{"@type":"Question","name":"Does JDK 24 fix virtual thread pinning on synchronized blocks?","acceptedAnswer":{"@type":"Answer","text":"Yes, JEP 491 in JDK 24 removes monitor-related carrier-thread pinning by tracking monitor ownership by virtual thread identity instead of carrier identity, letting virtual threads unmount inside synchronized blocks and reacquire monitors after waking. This resolved the stall class Netflix documented on Java 21. Residual pinning still occurs for native method frames, class loading, and local file I/O on Linux, tracked via the JFR jdk.VirtualThreadPinned event. Teams planning a JDK upgrade to unblock virtual thread rollouts can track fixes like JEP 491 through daily.dev."}},{"@type":"Question","name":"Should I migrate from Spring WebFlux to Spring MVC with virtual threads on JDK 25?","acceptedAnswer":{"@type":"Answer","text":"It depends on why WebFlux was adopted: services that chose it only to avoid blocking I/O at scale have a clear case to migrate, since blocking JDBC and JPA queries now scale acceptably on virtual threads with simpler code and readable stack traces. Services needing streaming, backpressure, server-sent events, or WebSockets should keep WebFlux, since virtual threads do not add backpressure semantics that Reactor provides. Engineers weighing Spring MVC versus WebFlux for their service architecture can compare trade-offs like these on daily.dev."}}]}
```

