import java.util.NoSuchElementException; /** * The core API (JEP 506, final in Java 25, no preview flag): newInstance(), where(...).run(...) / * .call(...), get(), isBound(), orElse(), orElseThrow(), and rebinding -- a nested where() that * shadows an outer binding for the extent of its own block, then hands control back to the outer * value once that block exits. * Chapter: docs/17-scoped-values-migration.md */ public class ScopedValuesBasics { static final ScopedValue USER = ScopedValue.newInstance(); public static void main(String[] args) { basicBindAndRead(); unboundThrowsByDefault(); orElseAndOrElseThrow(); rebindingShadowsThenRestores(); callReturnsAValue(); } static void basicBindAndRead() { ScopedValue.where(USER, "ankur").run(() -> { Check.that(USER.isBound(), "isBound() is true inside where().run()"); Check.that(USER.get().equals("ankur"), "get() returns \"ankur\" inside the binding"); deepMethodSeesTheSameBinding(); }); Check.that(!USER.isBound(), "isBound() is false again once run() has returned"); } static void deepMethodSeesTheSameBinding() { // No parameter was passed here -- USER is visible because this call happens ON THE SAME // THREAD while the binding is active, not because of anything passed explicitly. Check.that(USER.get().equals("ankur"), "a method several frames deep, given no parameter, still sees the binding"); } static void unboundThrowsByDefault() { try { USER.get(); Check.that(false, "expected get() to throw outside any binding"); } catch (NoSuchElementException e) { Check.that(true, "get() outside a binding throws NoSuchElementException, not null or a default value"); } } static void orElseAndOrElseThrow() { Check.that(USER.orElse("anonymous").equals("anonymous"), "orElse(fallback) returns the fallback when unbound, no exception"); ScopedValue.where(USER, "ankur").run(() -> Check.that(USER.orElse("anonymous").equals("ankur"), "orElse(fallback) returns the real value when bound")); try { USER.orElseThrow(() -> new IllegalStateException("no user in scope")); Check.that(false, "expected orElseThrow to throw when unbound"); } catch (IllegalStateException e) { Check.that(e.getMessage().equals("no user in scope"), "orElseThrow(supplier) throws exactly the supplied exception when unbound"); } } static void rebindingShadowsThenRestores() { ScopedValue.where(USER, "ankur").run(() -> { Check.that(USER.get().equals("ankur"), "outer binding: ankur"); ScopedValue.where(USER, "support-bot").run(() -> { // A nested where() on the SAME ScopedValue shadows the outer binding for the // extent of this inner block -- this is rebinding, and it's the mechanism a // request handler uses to say "everything I call from here on behaves as // support-bot" without touching the outer caller's binding at all. Check.that(USER.get().equals("support-bot"), "inner binding shadows the outer one: support-bot"); methodInsideInnerScope(); }); // Back outside the inner where(): the outer binding is exactly as it was, not // mutated, not cleared -- rebinding is scoped, not an assignment. Check.that(USER.get().equals("ankur"), "outer binding is restored, unchanged, once the inner where() block exits: ankur"); }); } static void methodInsideInnerScope() { Check.that(USER.get().equals("support-bot"), "a method called from inside the inner scope sees the REBOUND value, not the original"); } static void callReturnsAValue() { // .call(...) is where().run()'s twin for when you need a result back, propagating a // checked exception type X through Carrier.call's signature. int length = ScopedValue.where(USER, "ankur").call(() -> USER.get().length()); Check.that(length == 5, "call(...) returns whatever the lambda returns -- USER.get().length() == 5"); } }