{"id":1989,"date":"2025-09-24T16:03:37","date_gmt":"2025-09-24T04:03:37","guid":{"rendered":"https:\/\/www.ronella.xyz\/?p=1989"},"modified":"2026-01-12T09:01:02","modified_gmt":"2026-01-11T20:01:02","slug":"scoped-values-in-java-25-definitive-context-propagation-for-modern-java","status":"publish","type":"post","link":"https:\/\/www.ronella.xyz\/?p=1989","title":{"rendered":"Scoped Values in Java 25: Context Propagation for Modern Java"},"content":{"rendered":"<p>With the release of Java 25, scoped values come out of preview and enter the mainstream as one of the most impactful improvements for concurrency and context propagation in Java\u2019s recent history. Designed to address the perennial issue of safely sharing context across method chains and threads, scoped values deliver a clean, immutable, and automatically bounded solution that dethrones the error-prone ThreadLocal for most scenarios.<\/p>\n<h2>Rethinking Context: Why Scoped Values?<\/h2>\n<p>For years, Java developers have used ThreadLocal to pass data down a call stack\u2014such as security credentials, logging metadata, or request-specific state. While functional, ThreadLocal suffers from lifecycle ambiguity, memory leaks, and incompatibility with lightweight virtual threads. Scoped values solve these problems by making context propagation explicit in syntax, immutable in nature, and automatic in cleanup.<\/p>\n<h2>The Mental Model<\/h2>\n<p>Imagine code execution as moving through a series of rooms, each with its unique lighting. A scoped value is like setting the lighting for a room\u2014within its boundaries, everyone sees the same illumination (data value), but outside those walls, the setting is gone. Each scope block clearly defines where the data is available and safe to access.<\/p>\n<h2>How Scoped Values Work<\/h2>\n<p>Scoped values require two ingredients:<\/p>\n<ul>\n<li><strong>Declaration:<\/strong> Define a <code>ScopedValue<\/code> as a static final field, usually parameterized by the intended type.<\/li>\n<li><strong>Binding:<\/strong> Use <code>ScopedValue.where()<\/code> to create an execution scope where the value is accessible.<\/li>\n<\/ul>\n<p>Inside any method called within the binding scope\u2014even dozens of frames deep\u2014the value can be retrieved by <code>.get()<\/code>, without explicit parameter passing.<\/p>\n<h2>Example: Propagating User Context Across Methods<\/h2>\n<pre><code class=\"language-java\">\/\/ Declare the scoped value\nprivate static final ScopedValue&lt;String&gt; USERNAME = ScopedValue.newInstance();\n\npublic static void main(String[] args) {\n    ScopedValue.where(USERNAME, &quot;alice&quot;).run(() -&gt; entryPoint());\n}\n\nstatic void entryPoint() {\n    printCurrentUser();\n}\n\nstatic void printCurrentUser() {\n    System.out.println(&quot;Current user: &quot; + USERNAME.get()); \/\/ Outputs &quot;alice&quot;\n}<\/code><\/pre>\n<p>In this sample, <code>USERNAME<\/code> is accessible in any method within the binding scope, regardless of how far it\u2019s called from the entry point.<\/p>\n<h2>Nested Binding and Rebinding<\/h2>\n<p>Scoped values provide nested rebinding: within a scope, a method can establish a new nested binding for the same value, which is then available only to its callees. This ensures truly bounded context lifetimes and avoids unintended leakage or overwrites.<\/p>\n<pre><code class=\"language-java\">\/\/ Declare the scoped value\nprivate static final ScopedValue&lt;String&gt; MESSAGE = ScopedValue.newInstance();\n\nvoid foo() {\n    ScopedValue.where(MESSAGE, &quot;hello&quot;).run(() -&gt; bar());\n}\n\nvoid bar() {\n    System.out.println(MESSAGE.get()); \/\/ prints &quot;hello&quot;\n    ScopedValue.where(MESSAGE, &quot;goodbye&quot;).run(() -&gt; baz());\n    System.out.println(MESSAGE.get()); \/\/ prints &quot;hello&quot;\n}\n\nvoid baz() {\n    System.out.println(MESSAGE.get()); \/\/ prints &quot;goodbye&quot;\n}<\/code><\/pre>\n<p>Here, the value &quot;goodbye&quot; is only visible within the nested scope inside <code>baz<\/code>, while &quot;hello&quot; remains for <code>bar<\/code> outside that sub-scope.<a href=\"https:\/\/openjdk.org\/jeps\/506\">openjdk<\/a><\/p>\n<h2>Thread Safety and Structured Concurrency<\/h2>\n<p>Perhaps the biggest leap: scoped values are designed for modern concurrency, including Project Loom\u2019s virtual threads and Java's structured concurrency. Scoped values are automatically inherited by child threads launched within the scope, eliminating complex thread plumbing and ensuring correct context propagation.<\/p>\n<pre><code class=\"language-java\">\/\/ Declare the scoped value\nprivate static final ScopedValue&lt;String&gt; REQUEST_ID = ScopedValue.newInstance();\n\npublic static void main(String[] args) throws InterruptedException {\n    String requestId = &quot;req-789&quot;;\n    usingVirtualThreads(requestId);\n    usingStructuredConcurrency(requestId);\n}\n\nprivate static void usingVirtualThreads(String requestId) {\n    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {\n        \/\/ Launch multiple concurrent virtual threads, each with its own scoped value binding\n        Future&lt;?&gt; taskA = executor.submit(() -&gt;\n                ScopedValue.where(REQUEST_ID, requestId).run(() -&gt; processTask(&quot;Task VT A&quot;))\n        );\n        Future&lt;?&gt; taskB = executor.submit(() -&gt;\n                ScopedValue.where(REQUEST_ID, requestId).run(() -&gt; processTask(&quot;Task VT B&quot;))\n        );\n        Future&lt;?&gt; taskC = executor.submit(() -&gt;\n                ScopedValue.where(REQUEST_ID, requestId).run(() -&gt; processTask(&quot;Task VT C&quot;))\n        );\n\n        \/\/ Wait for all tasks to complete\n        try {\n            taskA.get();\n            taskB.get();\n            taskC.get();\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n\nprivate static void usingStructuredConcurrency(String requestId) {\n    ScopedValue.where(REQUEST_ID, requestId).run(() -&gt; {\n        try (var scope = StructuredTaskScope.open()) {\n            \/\/ Launch multiple concurrent virtual threads\n            scope.fork(() -&gt; {\n                processTask(&quot;Task SC A&quot;);\n            });\n            scope.fork(() -&gt; {\n                processTask(&quot;Task SC B&quot;);\n            });\n            scope.fork(() -&gt; {\n                processTask(&quot;Task SC C&quot;);\n            });\n\n            scope.join();\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(e);\n        }\n    });\n}\n\nprivate static void processTask(String taskName) {\n    \/\/ Scoped value REQUEST_ID is automatically visible here\n    System.out.println(taskName + &quot; processing request: &quot; + REQUEST_ID.get());\n}<\/code><\/pre>\n<p>No need for explicit context passing\u2014child threads see the intended value automatically.<\/p>\n<h2>Key Features and Advantages<\/h2>\n<ul>\n<li><strong>Immutability:<\/strong> Values cannot be mutated within scope, preventing accidental overwrite and race conditions.<\/li>\n<li><strong>Automatic Cleanup:<\/strong> Context disappears at the end of the scope, eliminating leaks.<\/li>\n<li><strong>No Boilerplate:<\/strong> No more manual parameter threading across dozens of method signatures.<\/li>\n<li><strong>Designed for Virtual Threads:<\/strong> Plays perfectly with Java\u2019s latest concurrency primitives.<a href=\"https:\/\/www.baeldung.com\/java-25-features\">baeldung+2<\/a><\/li>\n<\/ul>\n<h2>Use Cases<\/h2>\n<ul>\n<li>Securely propagate authenticated user or tracing info in web servers.<\/li>\n<li>Pass tenant, locale, metrics, or logger context across libraries.<\/li>\n<li>Enable robust structured concurrency with context auto-inheritance.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>With the release of Java 25, scoped values come out of preview and enter the mainstream as one of the most impactful improvements for concurrency and context propagation in Java\u2019s recent history. Designed to address the perennial issue of safely sharing context across method chains and threads, scoped values deliver a clean, immutable, and automatically [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[89],"tags":[],"_links":{"self":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/1989"}],"collection":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1989"}],"version-history":[{"count":2,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/1989\/revisions"}],"predecessor-version":[{"id":2046,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=\/wp\/v2\/posts\/1989\/revisions\/2046"}],"wp:attachment":[{"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1989"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1989"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ronella.xyz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1989"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}