diff a/src/java.base/share/classes/java/lang/ScopedValue.java b/src/java.base/share/classes/java/lang/ScopedValue.java
--- a/src/java.base/share/classes/java/lang/ScopedValue.java
+++ b/src/java.base/share/classes/java/lang/ScopedValue.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2022, Red Hat Inc.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
@@ -27,11 +27,10 @@
 package java.lang;
 
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.lang.ref.Reference;
-import java.util.concurrent.Callable;
 import java.util.concurrent.StructuredTaskScope;
 import java.util.concurrent.StructureViolationException;
 import java.util.function.Supplier;
 import jdk.internal.access.JavaUtilConcurrentTLRAccess;
 import jdk.internal.access.SharedSecrets;
@@ -62,33 +61,33 @@
  * object <em>bound</em> to some value for the bounded period of execution of a method.
  * The method may invoke another method, which in turn may invoke another. The unfolding
  * execution of the methods define a <em>dynamic scope</em>. Code in these methods with
  * access to the {@code ScopedValue} object may read its value. The {@code ScopedValue}
  * object reverts to being <em>unbound</em> when the original method completes normally or
- * with an exception. The {@code ScopedValue} API supports executing a {@link Runnable#run()
- * Runnable.run}, {@link Callable#call() Callable.call}, or {@link Supplier#get() Supplier.get}
- * method with a {@code ScopedValue} bound to a value.
+ * with an exception. The {@code ScopedValue} API supports executing a {@link Runnable},
+ * or {@link CallableOp} with a {@code ScopedValue} bound to a value.
  *
  * <p> Consider the following example with a scoped value "{@code NAME}" bound to the value
- * "{@code duke}" for the execution of a {@code run} method. The {@code run} method, in
- * turn, invokes {@code doSomething}.
+ * "{@code duke}" for the execution of a {@code Runnable}'s {@code run} method.
+ * The {@code run} method, in turn, invokes a method {@code doSomething}.
+ *
+ *
  * {@snippet lang=java :
  *     // @link substring="newInstance" target="#newInstance" :
  *     private static final ScopedValue<String> NAME = ScopedValue.newInstance();
  *
- *     // @link substring="runWhere" target="#runWhere" :
+ *     // @link substring="runWhere" target="#runWhere(ScopedValue, Object, Runnable)" :
  *     ScopedValue.runWhere(NAME, "duke", () -> doSomething());
  * }
  * Code executed directly or indirectly by {@code doSomething}, with access to the field
  * {@code NAME}, can invoke {@code NAME.get()} to read the value "{@code duke}". {@code
  * NAME} is bound while executing the {@code run} method. It reverts to being unbound when
  * the {@code run} method completes.
  *
  * <p> The example using {@code runWhere} invokes a method that does not return a result.
- * The {@link #callWhere(ScopedValue, Object, Callable) callWhere} and {@link
- * #getWhere(ScopedValue, Object, Supplier) getWhere} can be used to invoke a method that
- * returns a result.
+ * The {@link #callWhere(ScopedValue, Object, CallableOp) callWhere} method can be used
+ * to invoke a method that returns a result.
  * In addition, {@code ScopedValue} defines the {@link #where(ScopedValue, Object)} method
  * for cases where multiple mappings (of {@code ScopedValue} to value) are accumulated
  * in advance of calling a method with all {@code ScopedValue}s bound to their value.
  *
  * <h2>Bindings are per-thread</h2>
@@ -141,11 +140,11 @@
  * {@code ScopedValue} supports sharing across threads. This sharing is limited to
  * structured cases where child threads are started and terminate within the bounded
  * period of execution by a parent thread. When using a {@link StructuredTaskScope},
  * scoped value bindings are <em>captured</em> when creating a {@code StructuredTaskScope}
  * and inherited by all threads started in that task scope with the
- * {@link StructuredTaskScope#fork(Callable) fork} method.
+ * {@link StructuredTaskScope#fork(java.util.concurrent.Callable) fork} method.
  *
  * <p> A {@code ScopedValue} that is shared across threads requires that the value be an
  * immutable object or for all access to the value to be appropriately synchronized.
  *
  * <p> In the following example, the {@code ScopedValue} {@code NAME} is bound to the
@@ -289,17 +288,16 @@
     }
 
     /**
      * A mapping of scoped values, as <em>keys</em>, to values.
      *
-     * <p> A {@code Carrier} is used to accumulate mappings so that an operation (a
-     * {@link Runnable} or {@link Callable}) can be executed with all scoped values in the
+     * <p> A {@code Carrier} is used to accumulate mappings so that an operation (a {@link
+     * Runnable} or {@link CallableOp}) can be executed with all scoped values in the
      * mapping bound to values. The following example runs an operation with {@code k1}
      * bound (or rebound) to {@code v1}, and {@code k2} bound (or rebound) to {@code v2}.
      * {@snippet lang=java :
-     *     // @link substring="where" target="#where(ScopedValue, Object)" :
-     *     ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );
+     *     ScopedValue.runWhere(ScopedValue.where(k1, v1).where(k2, v2), () -> ... );
      * }
      *
      * <p> A {@code Carrier} is immutable and thread-safe. The {@link
      * #where(ScopedValue, Object) where} method returns a new {@code Carrier} object,
      * it does not mutate an existing mapping.
@@ -381,11 +379,11 @@
             for (Carrier carrier = this;
                  carrier != null && containsAll(carrier.bitmask, bits);
                  carrier = carrier.prev) {
                 if (carrier.getKey() == key) {
                     Object value = carrier.get();
-                    return (T)value;
+                    return (T) value;
                 }
             }
             throw new NoSuchElementException();
         }
 
@@ -404,76 +402,34 @@
          * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
          * is closed and {@link StructureViolationException} is thrown.
          *
          * @param op the operation to run
          * @param <R> the type of the result of the operation
+         * @param <X> type of the exception thrown by the operation
          * @return the result
          * @throws StructureViolationException if a structure violation is detected
-         * @throws Exception if {@code op} completes with an exception
-         * @see ScopedValue#callWhere(ScopedValue, Object, Callable)
+         * @throws X if {@code op} completes with an exception
+         * @see ScopedValue#callWhere(ScopedValue, Object, CallableOp)
          */
-        public <R> R call(Callable<? extends R> op) throws Exception {
+        <R, X extends Throwable> R call(CallableOp<? extends R, X> op) throws X {
             Objects.requireNonNull(op);
             Cache.invalidate(bitmask);
             var prevSnapshot = scopedValueBindings();
             var newSnapshot = new Snapshot(this, prevSnapshot);
             return runWith(newSnapshot, op);
         }
 
-        /**
-         * Invokes a supplier of results with each scoped value in this mapping bound
-         * to its value in the current thread.
-         * When the operation completes (normally or with an exception), each scoped value
-         * in the mapping will revert to being unbound, or revert to its previous value
-         * when previously bound, in the current thread. If {@code op} completes with an
-         * exception then it propagated by this method.
-         *
-         * <p> Scoped values are intended to be used in a <em>structured manner</em>. If code
-         * invoked directly or indirectly by the operation creates a {@link StructuredTaskScope}
-         * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
-         * as a <em>structure violation</em> when the operation completes (normally or with an
-         * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
-         * is closed and {@link StructureViolationException} is thrown.
-         *
-         * @param op the operation to run
-         * @param <R> the type of the result of the operation
-         * @return the result
-         * @throws StructureViolationException if a structure violation is detected
-         * @see ScopedValue#getWhere(ScopedValue, Object, Supplier)
-         */
-        public <R> R get(Supplier<? extends R> op) {
-            Objects.requireNonNull(op);
-            Cache.invalidate(bitmask);
-            var prevSnapshot = scopedValueBindings();
-            var newSnapshot = new Snapshot(this, prevSnapshot);
-            return runWith(newSnapshot, new CallableAdapter<R>(op));
-        }
-
-        // A lightweight adapter from Supplier to Callable. This is
-        // used here to create the Callable which is passed to
-        // Carrier#call() in this thread because it needs neither
-        // runtime bytecode generation nor any release fencing.
-        private static final class CallableAdapter<V> implements Callable<V> {
-            private /*non-final*/ Supplier<? extends V> s;
-            CallableAdapter(Supplier<? extends V> s) {
-                this.s = s;
-            }
-            public V call() {
-                return s.get();
-            }
-        }
-
         /**
          * Execute the action with a set of ScopedValue bindings.
          *
          * The VM recognizes this method as special, so any changes to the
          * name or signature require corresponding changes in
          * JVM_FindScopedValueBindings().
          */
         @Hidden
         @ForceInline
-        private <R> R runWith(Snapshot newSnapshot, Callable<R> op) {
+        private <R, X extends Throwable> R runWith(Snapshot newSnapshot, CallableOp<R, X> op) {
             try {
                 Thread.setScopedValueBindings(newSnapshot);
                 Thread.ensureMaterializedForStackWalk(newSnapshot);
                 return ScopedValueContainer.call(op);
             } finally {
@@ -500,11 +456,11 @@
          *
          * @param op the operation to run
          * @throws StructureViolationException if a structure violation is detected
          * @see ScopedValue#runWhere(ScopedValue, Object, Runnable)
          */
-        public void run(Runnable op) {
+        void run(Runnable op) {
             Objects.requireNonNull(op);
             Cache.invalidate(bitmask);
             var prevSnapshot = scopedValueBindings();
             var newSnapshot = new Snapshot(this, prevSnapshot);
             runWith(newSnapshot, op);
@@ -530,19 +486,39 @@
                 Cache.invalidate(bitmask);
             }
         }
     }
 
+    /**
+     * An operation that returns a result and may throw an exception.
+     *
+     * @apiNote Naming TBD. Not proposing to move this to j.u.function.
+     *
+     * @param <T> result type of the operation
+     * @param <X> type of the exception thrown by the operation
+     * @since 23
+     */
+    @PreviewFeature(feature = PreviewFeature.Feature.SCOPED_VALUES)
+    @FunctionalInterface
+    public interface CallableOp<T, X extends Throwable> {
+        /**
+         * Executes this operation.
+         * @return the result, can be null
+         * @throws X if the operation completes with an exception
+         */
+        T call() throws X;
+    }
+
     /**
      * Creates a new {@code Carrier} with a single mapping of a {@code ScopedValue}
      * <em>key</em> to a value. The {@code Carrier} can be used to accumulate mappings so
      * that an operation can be executed with all scoped values in the mapping bound to
      * values. The following example runs an operation with {@code k1} bound (or rebound)
      * to {@code v1}, and {@code k2} bound (or rebound) to {@code v2}.
      * {@snippet lang=java :
-     *     // @link substring="run" target="Carrier#run(Runnable)" :
-     *     ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );
+     *     // @link substring="runWhere" target="#runWhere(Carrier, Runnable)" :
+     *     ScopedValue.runWhere(ScopedValue.where(k1, v1).where(k2, v2), () -> ... );
      * }
      *
      * @param key the {@code ScopedValue} key
      * @param value the value, can be {@code null}
      * @param <T> the type of the value
@@ -564,65 +540,54 @@
      * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
      * as a <em>structure violation</em> when the operation completes (normally or with an
      * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
      * is closed and {@link StructureViolationException} is thrown.
      *
-     * @implNote
-     * This method is implemented to be equivalent to:
-     * {@snippet lang=java :
-     *     // @link substring="call" target="Carrier#call(Callable)" :
-     *     ScopedValue.where(key, value).call(op);
-     * }
-     *
      * @param key the {@code ScopedValue} key
      * @param value the value, can be {@code null}
      * @param <T> the type of the value
      * @param <R> the result type
+     * @param <X> type of the exception thrown by the operation
      * @param op the operation to call
      * @return the result
      * @throws StructureViolationException if a structure violation is detected
-     * @throws Exception if the operation completes with an exception
+     * @throws X if the operation completes with an exception
+     * @since 23
      */
-    public static <T, R> R callWhere(ScopedValue<T> key,
-                                     T value,
-                                     Callable<? extends R> op) throws Exception {
+    public static <T, R, X extends Throwable> R callWhere(ScopedValue<T> key,
+                                                          T value,
+                                                          CallableOp<? extends R, X> op) throws X {
         return where(key, value).call(op);
     }
 
     /**
-     * Invokes a supplier of results with a {@code ScopedValue} bound to a value
-     * in the current thread. When the operation completes (normally or with an
-     * exception), the {@code ScopedValue} will revert to being unbound, or revert to
-     * its previous value when previously bound, in the current thread. If {@code op}
-     * completes with an exception then it propagated by this method.
+     * Calls a value-returning operation with each scoped value in the given mapping bound
+     * to its value in the current thread.
+     * When the operation completes (normally or with an exception), each scoped value
+     * in the mapping will revert to being unbound, or revert to its previous value
+     * when previously bound, in the current thread. If {@code op} completes with an
+     * exception then it propagated by this method.
      *
      * <p> Scoped values are intended to be used in a <em>structured manner</em>. If code
      * invoked directly or indirectly by the operation creates a {@link StructuredTaskScope}
      * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
      * as a <em>structure violation</em> when the operation completes (normally or with an
      * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
      * is closed and {@link StructureViolationException} is thrown.
      *
-     * @implNote
-     * This method is implemented to be equivalent to:
-     * {@snippet lang=java :
-     *     // @link substring="get" target="Carrier#get(Supplier)" :
-     *     ScopedValue.where(key, value).get(op);
-     * }
-     *
-     * @param key the {@code ScopedValue} key
-     * @param value the value, can be {@code null}
-     * @param <T> the type of the value
-     * @param <R> the result type
-     * @param op the operation to call
+     * @param carrier the mapping of scoped values, as keys, to values
+     * @param op the operation to run
      * @return the result
+     * @param <R> the result type
+     * @param <X> type of the exception thrown by the operation
      * @throws StructureViolationException if a structure violation is detected
+     * @throws X if the operation completes with an exception
+     * @since 23
      */
-    public static <T, R> R getWhere(ScopedValue<T> key,
-                                    T value,
-                                    Supplier<? extends R> op) {
-        return where(key, value).get(op);
+    public static <R, X extends Throwable> R callWhere(Carrier carrier,
+                                                       CallableOp<? extends R, X> op) throws X {
+        return carrier.call(op);
     }
 
     /**
      * Run an operation with a {@code ScopedValue} bound to a value in the current
      * thread. When the operation completes (normally or with an exception), the
@@ -635,27 +600,44 @@
      * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
      * as a <em>structure violation</em> when the operation completes (normally or with an
      * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
      * is closed and {@link StructureViolationException} is thrown.
      *
-     * @implNote
-     * This method is implemented to be equivalent to:
-     * {@snippet lang=java :
-     *     // @link substring="run" target="Carrier#run(Runnable)" :
-     *     ScopedValue.where(key, value).run(op);
-     * }
-     *
      * @param key the {@code ScopedValue} key
      * @param value the value, can be {@code null}
      * @param <T> the type of the value
      * @param op the operation to call
      * @throws StructureViolationException if a structure violation is detected
      */
     public static <T> void runWhere(ScopedValue<T> key, T value, Runnable op) {
         where(key, value).run(op);
     }
 
+    /**
+     * Runs an operation with each scoped value in the given mapping bound to its value
+     * in the current thread.
+     * When the operation completes (normally or with an exception), each scoped value
+     * in the mapping will revert to being unbound, or revert to its previous value
+     * when previously bound, in the current thread. If {@code op} completes with an
+     * exception then it propagated by this method.
+     *
+     * <p> Scoped values are intended to be used in a <em>structured manner</em>. If code
+     * invoked directly or indirectly by the operation creates a {@link StructuredTaskScope}
+     * but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
+     * as a <em>structure violation</em> when the operation completes (normally or with an
+     * exception). In that case, the underlying construct of the {@code StructuredTaskScope}
+     * is closed and {@link StructureViolationException} is thrown.
+     *
+     * @param carrier the mapping of scoped values, as keys, to values
+     * @param op the operation to run
+     * @throws StructureViolationException if a structure violation is detected
+     * @since 23
+     */
+    public static void runWhere(Carrier carrier, Runnable op) {
+        carrier.run(op);
+    }
+
     private ScopedValue() {
         this.hash = generateKey();
     }
 
     /**
