Oracle engineers have spent years refining a new way to pass data through layers of Java code without the headaches that come with traditional thread-local variables. The result? Scoped values. Now finalized in JDK 25 via JEP 506 from OpenJDK, they promise simpler, safer and more efficient data sharing. Especially when paired with virtual threads.
But here’s the twist. The proposal that kicked off this article’s research, JEP 541, has nothing to do with concurrency. It deprecates the macOS/x64 port for removal starting in JDK 27. Apple long ago moved on to its own silicon. Oracle followed suit. “Oracle engineers will thus stop maintaining the macOS/x64 port as of JDK 27,” the JEP states plainly. Maintaining it had become a significant burden. A recent Register article captured the moment. Building for the old platform now requires a special flag. No promises it will even work.
And yet. The two topics intersect in a larger story about Java’s evolution. One prunes legacy support to free resources. The other modernizes core concurrency primitives for an era of millions of lightweight threads. Developers on Intel Macs will soon need to switch to ARM. At the same time, server-side teams handling massive request volumes gain better tools for context propagation.
Scoped values first appeared in experimental form back in JDK 20. They went through multiple preview stages. Incubation in JEP 429. Previews in subsequent releases. Refinements continued through JDK 24’s fourth preview under JEP 487. That version removed convenience methods like callWhere and runWhere. The API became fully fluent. Only Carrier.call and Carrier.run remained. “We removed the callWhere and runWhere methods from the ScopedValue class, leaving the API completely fluent,” the JEP 487 text noted.
By JDK 25 the feature reached final status. One small adjustment. The orElse method no longer accepts null. Andrew Haley owns the JEP 506 proposal. Andrew Dinn contributed as author. Alan Bateman reviewed it. Paul Sandoz endorsed the work. The summary reads straightforward. “Introduce scoped values, which enable a method to share immutable data both with its callees within a thread, and with child threads. Scoped values are easier to reason about than thread-local variables. They also have lower space and time costs, especially when used together with virtual threads and structured concurrency.”
Why the change? ThreadLocal has served Java since version 1.2. It works. Yet it carries flaws that grew more obvious with virtual threads. Virtual threads, stabilized in JDK 21, number in the hundreds of thousands or millions without much trouble. Each one carrying its own ThreadLocal map adds up. Memory. Time. And cleanup headaches.
Three problems stand out. Unconstrained mutability. Any code with access to the ThreadLocal reference can change its value at any time. Data flow becomes hard to trace. Unbounded lifetime. Values linger until remove gets called or the thread dies. Forgetting remove in a thread pool leads to leaks. Or worse, security issues when context from one request bleeds into another. Expensive inheritance. Child threads copy the entire map from parents. Costly when thousands of children spawn.
Scoped values address these directly. They bind data for a limited scope. The binding happens through a where method chained with run or call. Inside that block and any methods it calls, the value appears. Outside, it doesn’t. Immutable by design. The value can’t change once bound. Child threads started with structured concurrency inherit the values efficiently. No full copy. Just a reference. Performance gains follow.
Consider a web framework. It needs to pass a context object containing user ID, transaction details, database connections. Without scoped values or thread locals, every method in the call chain must accept an extra parameter. Even methods that don’t use it. User code gets polluted with framework internals. ThreadLocal avoids the parameter. But at the cost of the problems above.
With scoped values the framework declares a static final ScopedValue. In the entry point it binds the context. Then it calls the application handler. The handler, and any code it invokes, can read the value without parameters. When the binding block ends, the value disappears. Automatic. No explicit cleanup. No leaks.
The code looks like this in practice. ScopedValue.where(CONTEXT, context).run(() -> application.handle(request, response)); Inside handle or deeper methods, CONTEXT.get() returns the bound value. Or use orElse for a default. But not null anymore in the final API.
Rebinding works too. Nested calls to where create inner scopes with different values. The outer binding reappears when the inner scope ends. Clear. Predictable. Structured concurrency complements this. When forking child tasks, the scoped values pass along automatically. Errors and cancellation propagate properly. The combination reduces complexity in concurrent code.
Early feedback during previews shaped the design. The fourth preview in JDK 24 focused on fluency. Chained where calls replaced nested ones in some cases. No more set method on the value itself. One-way flow from binder to readers. That matches most real uses. Frameworks passing context down. Not bidirectional communication.
Performance numbers from Oracle and community tests show advantages. Especially with virtual threads. Lower memory footprint. Faster access in deep call stacks. No per-thread map maintenance for every possible scoped value. Only the ones actually bound in that thread’s current scopes.
Yet ThreadLocal stays. The JEP makes clear it is not a goal to deprecate the old API. Many libraries and applications depend on it. Some need the mutability or different inheritance behavior. Migration remains optional. Teams can adopt scoped values where they fit. Often in new code or performance-sensitive paths.
Recent discussions on X reflect adoption. One post from late 2025 highlighted Java 25 as bringing “memory-safe virtual threads” through scoped values. Another noted the finalization alongside structured concurrency in the LTS release. BellSoft shared a small example in April 2026 demonstrating context sharing. Developers praised the clarity over ThreadLocal.
InfoWorld covered JDK 24’s arrival in March 2025. It listed scoped values among two dozen features. By then the fourth preview had simplified the API further. Foojay.io and BellSoft blogs explained the fluent style. Removal of older methods reduced confusion. Nicolai Parlog’s Inside Java newscasts walked through the changes. Practical examples helped teams experiment.
The macOS/x64 story runs parallel but separate. Apple’s hardware transition left Intel Macs behind. Oracle decided enough was enough. From JDK 27 the port requires –enable-deprecated-ports to even attempt a build. Warnings appear. No guarantees. The Register piece from June 2026 quoted the JEP directly. Community reaction on Reddit mixed. Some lamented the end of easy Intel support. Others accepted the reality. ARM dominates new Macs anyway. Most production Java runs on Linux servers these days.
Taken together these moves show OpenJDK’s priorities. Streamline the platform. Focus effort on features that matter to modern workloads. Concurrency improvements for cloud-scale applications. Pruning ports that few maintain. Both reduce long-term costs. Both make the platform cleaner.
Developers should start experimenting now. Enable preview features in JDK 24 or 25 builds. Try scoped values in test applications. Combine them with StructuredTaskScope for concurrent work. Measure the difference in memory and speed. Especially under load with many virtual threads. The bounded lifetime alone eliminates entire classes of bugs.
Framework authors face interesting choices. Many already updated to virtual threads. Adding scoped value support for context could simplify their APIs. No more hidden ThreadLocal maps. Clearer contracts with application code. Less risk of context leakage.
Of course challenges remain. Not every use case fits the immutable, one-way model. Some legacy code relies heavily on mutable thread locals. Refactoring takes time. Education matters. Teams need to understand when to reach for each tool. The JEP authors anticipated this. They kept both mechanisms available.
Looking ahead, further refinements could appear. Integration with other Loom features. Perhaps additional convenience methods once usage patterns solidify. But the core design seems settled. Bounded. Immutable. Efficient. Easier to reason about.
Java continues its long refinement. Not flashy changes that rewrite the language. Steady, careful improvements that pay dividends at scale. Scoped values fit that pattern. They solve real pain points exposed by virtual threads. They do so without breaking existing code. The deprecation of macOS/x64 does the same in its own way. Acknowledges hardware reality. Frees maintainers to focus elsewhere.
Both signal a platform maturing. Ready for the next decade of server, cloud and high-throughput computing. Developers who embrace the new tools will likely see cleaner code and better performance. Those maintaining old Intel-based build pipelines should plan their migration. The clock ticks toward JDK 27.


WebProNews is an iEntry Publication