This is an automated email from the ASF dual-hosted git repository.

lprimak pushed a commit to branch 3.x
in repository https://gitbox.apache.org/repos/asf/shiro.git


The following commit(s) were added to refs/heads/3.x by this push:
     new 4c058a2f7 [#1862] [3.0] Support for JDK 25 scoped values (#2485)
4c058a2f7 is described below

commit 4c058a2f79fa55adc8f850c03b72c4deb3a71d6f
Author: Lenny Primak <[email protected]>
AuthorDate: Sun Feb 22 10:02:35 2026 -0600

    [#1862] [3.0] Support for JDK 25 scoped values (#2485)
---
 .../main/java/org/apache/shiro/SecurityUtils.java  |  14 +-
 .../java/org/apache/shiro/subject/Subject.java     |  39 +++--
 .../shiro/subject/support/SubjectCallable.java     |  19 ++-
 .../shiro/subject/support/SubjectRunnable.java     |  20 ++-
 .../shiro/subject/support/SubjectThreadState.java  |  21 ++-
 .../org/apache/shiro/util/DefaultScopedValues.java |  29 ++++
 .../org/apache/shiro/util/JavaEnvironment.java     | 181 ---------------------
 .../java/org/apache/shiro/util/ScopedValues.java   |  55 +++++++
 .../org/apache/shiro/util/DefaultScopedValues.java |  60 +++++++
 .../apache/shiro/testing/jaxrs/WhoamiResource.java |  16 +-
 .../org/apache/shiro/guice/ShiroSessionScope.java  |   8 +-
 .../org/apache/shiro/ee/filters/ShiroFilter.java   |   4 +-
 12 files changed, 244 insertions(+), 222 deletions(-)

diff --git a/core/src/main/java/org/apache/shiro/SecurityUtils.java 
b/core/src/main/java/org/apache/shiro/SecurityUtils.java
index e53b61b2f..68758c828 100644
--- a/core/src/main/java/org/apache/shiro/SecurityUtils.java
+++ b/core/src/main/java/org/apache/shiro/SecurityUtils.java
@@ -21,6 +21,7 @@ package org.apache.shiro;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.mgt.WrappedSecurityManager;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ScopedValues;
 import org.apache.shiro.util.ThreadContext;
 import java.util.Objects;
 import java.util.function.Predicate;
@@ -54,6 +55,11 @@ public abstract class SecurityUtils {
      *                               - a Subject should <em>always</em> be 
available to the caller.
      */
     public static Subject getSubject() {
+        if (ScopedValues.INSTANCE.isSupported() && 
ScopedValues.INSTANCE.isBound()) {
+            return ScopedValues.INSTANCE.get().subject();
+        }
+
+        // fallback to ThreadContext
         Subject subject = ThreadContext.getSubject();
         if (subject == null) {
             subject = (new Subject.Builder()).buildSubject();
@@ -114,6 +120,10 @@ public abstract class SecurityUtils {
      *                                             calling code, which 
typically indicates an invalid application configuration.
      */
     public static SecurityManager getSecurityManager() throws 
UnavailableSecurityManagerException {
+        if (ScopedValues.INSTANCE.isSupported() && 
ScopedValues.INSTANCE.isBound()) {
+            return ScopedValues.INSTANCE.get().securityManager();
+        }
+
         SecurityManager securityManager = ThreadContext.getSecurityManager();
         if (securityManager == null) {
             securityManager = SecurityUtils.securityManager;
@@ -178,8 +188,8 @@ public abstract class SecurityUtils {
     public static <SM extends SecurityManager> SM
     unwrapSecurityManager(SecurityManager securityManager, Class<SM> type,
                           Predicate<Class<? extends SecurityManager>> 
predicate) {
-        while (securityManager instanceof WrappedSecurityManager && 
!predicate.test(securityManager.getClass())) {
-            WrappedSecurityManager wrappedSecurityManager = 
(WrappedSecurityManager) securityManager;
+        while (securityManager instanceof WrappedSecurityManager 
wrappedSecurityManager
+                && !predicate.test(securityManager.getClass())) {
             securityManager = wrappedSecurityManager.unwrap();
             if (securityManager == wrappedSecurityManager) {
                 throw new IllegalStateException("SecurityManager 
implementation of type [" + type.getName()
diff --git a/core/src/main/java/org/apache/shiro/subject/Subject.java 
b/core/src/main/java/org/apache/shiro/subject/Subject.java
index b01dae04d..f690cd34b 100644
--- a/core/src/main/java/org/apache/shiro/subject/Subject.java
+++ b/core/src/main/java/org/apache/shiro/subject/Subject.java
@@ -609,18 +609,17 @@ public interface Subject {
          */
         private final SubjectContext subjectContext;
 
-        /**
-         * The SecurityManager to invoke during the {@link #buildSubject} call.
-         */
-        private final SecurityManager securityManager;
-
         /**
          * Constructs a new {@link Subject.Builder} instance, using the {@code 
SecurityManager} instance available
          * to the calling code as determined by a call to {@link 
org.apache.shiro.SecurityUtils#getSecurityManager()}
          * to build the {@code Subject} instance.
          */
         public Builder() {
-            this(SecurityUtils.getSecurityManager());
+            this.subjectContext = newSubjectContextInstance();
+            if (this.subjectContext == null) {
+                throw new IllegalStateException("Subject instance returned 
from 'newSubjectContextInstance' "
+                        + "cannot be null.");
+            }
         }
 
         /**
@@ -630,15 +629,10 @@ public interface Subject {
          * @param securityManager the {@code SecurityManager} to use when 
building the {@code Subject} instance.
          */
         public Builder(SecurityManager securityManager) {
+            this();
             if (securityManager == null) {
                 throw new NullPointerException("SecurityManager method 
argument cannot be null.");
             }
-            this.securityManager = securityManager;
-            this.subjectContext = newSubjectContextInstance();
-            if (this.subjectContext == null) {
-                throw new IllegalStateException("Subject instance returned 
from 'newSubjectContextInstance' "
-                        + "cannot be null.");
-            }
             this.subjectContext.setSecurityManager(securityManager);
         }
 
@@ -662,6 +656,21 @@ public interface Subject {
             return this.subjectContext;
         }
 
+        /**
+         * Uses the provided security manager to build a {@link Subject} 
instance based on the
+         * {@link SubjectContext} information collected by this {@code 
Builder} instance.
+         *
+         * @return this builder
+         * @throws IllegalStateException if no {@code SecurityManager} is null
+         */
+        public Builder securityManager(SecurityManager securityManager) {
+            if (securityManager == null) {
+                throw new NullPointerException("SecurityManager method 
argument cannot be null.");
+            }
+            this.subjectContext.setSecurityManager(securityManager);
+            return this;
+        }
+
         /**
          * Enables building a {@link Subject Subject} instance that owns the 
{@link Session Session} with the
          * specified {@code sessionId}.
@@ -841,8 +850,10 @@ public interface Subject {
          * other methods in this class.
          */
         public Subject buildSubject() {
-            return this.securityManager.createSubject(this.subjectContext);
+            if (this.subjectContext.getSecurityManager() == null) {
+                
this.subjectContext.setSecurityManager(SecurityUtils.getSecurityManager());
+            }
+            return 
this.subjectContext.getSecurityManager().createSubject(this.subjectContext);
         }
     }
-
 }
diff --git 
a/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java 
b/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java
index d6d1ecfcb..de2277987 100644
--- a/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java
+++ b/core/src/main/java/org/apache/shiro/subject/support/SubjectCallable.java
@@ -18,7 +18,9 @@
  */
 package org.apache.shiro.subject.support;
 
+import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ScopedValues;
 import org.apache.shiro.util.ThreadState;
 
 import java.util.concurrent.Callable;
@@ -62,13 +64,15 @@ public class SubjectCallable<V> implements Callable<V> {
 
     protected final ThreadState threadState;
     private final Callable<V> callable;
+    private final SecurityManager securityManager;
+    private final Subject subject;
 
     public SubjectCallable(Subject subject, Callable<V> delegate) {
-        this(new SubjectThreadState(subject), delegate);
+        this(subject, ScopedValues.INSTANCE.isSupported() ? null : new 
SubjectThreadState(subject), delegate);
     }
 
-    protected SubjectCallable(ThreadState threadState, Callable<V> delegate) {
-        if (threadState == null) {
+    protected SubjectCallable(Subject subject, ThreadState threadState, 
Callable<V> delegate) {
+        if (threadState == null && !ScopedValues.INSTANCE.isSupported()) {
             throw new IllegalArgumentException("ThreadState argument cannot be 
null.");
         }
         this.threadState = threadState;
@@ -76,9 +80,16 @@ public class SubjectCallable<V> implements Callable<V> {
             throw new IllegalArgumentException("Callable delegate instance 
cannot be null.");
         }
         this.callable = delegate;
+        this.subject = subject;
+        this.securityManager = SubjectThreadState.getSecurityManager(subject);
     }
 
     public V call() throws Exception {
+        if (ScopedValues.INSTANCE.isSupported()) {
+            return ScopedValues.INSTANCE.call(this, callable, subject, 
securityManager);
+        }
+
+        // fallback to ThreadState binding if ScopedValues are not available
         try {
             threadState.bind();
             return doCall(this.callable);
@@ -87,7 +98,7 @@ public class SubjectCallable<V> implements Callable<V> {
         }
     }
 
-    protected V doCall(Callable<V> target) throws Exception {
+    public V doCall(Callable<V> target) throws Exception {
         return target.call();
     }
 }
diff --git 
a/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java 
b/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java
index 42eeb61bd..7fd93e90d 100644
--- a/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java
+++ b/core/src/main/java/org/apache/shiro/subject/support/SubjectRunnable.java
@@ -18,7 +18,9 @@
  */
 package org.apache.shiro.subject.support;
 
+import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ScopedValues;
 import org.apache.shiro.util.ThreadState;
 
 /**
@@ -58,6 +60,8 @@ public class SubjectRunnable implements Runnable {
 
     protected final ThreadState threadState;
     private final Runnable runnable;
+    private final SecurityManager securityManager;
+    private final Subject subject;
 
     /**
      * Creates a new {@code SubjectRunnable} that, when executed, will execute 
the target {@code delegate}, but
@@ -67,7 +71,7 @@ public class SubjectRunnable implements Runnable {
      * @param delegate the runnable to run.
      */
     public SubjectRunnable(Subject subject, Runnable delegate) {
-        this(new SubjectThreadState(subject), delegate);
+        this(subject, ScopedValues.INSTANCE.isSupported() ? null : new 
SubjectThreadState(subject), delegate);
     }
 
     /**
@@ -79,8 +83,8 @@ public class SubjectRunnable implements Runnable {
      * @param delegate    the delegate {@code Runnable} to execute when this 
instance is {@link #run() run()}.
      * @throws IllegalArgumentException if either the {@code ThreadState} or 
{@link Runnable} arguments are {@code null}.
      */
-    protected SubjectRunnable(ThreadState threadState, Runnable delegate) 
throws IllegalArgumentException {
-        if (threadState == null) {
+    protected SubjectRunnable(Subject subject, ThreadState threadState, 
Runnable delegate) throws IllegalArgumentException {
+        if (threadState == null && !ScopedValues.INSTANCE.isSupported()) {
             throw new IllegalArgumentException("ThreadState argument cannot be 
null.");
         }
         this.threadState = threadState;
@@ -88,6 +92,8 @@ public class SubjectRunnable implements Runnable {
             throw new IllegalArgumentException("Runnable argument cannot be 
null.");
         }
         this.runnable = delegate;
+        this.subject = subject;
+        this.securityManager = SubjectThreadState.getSecurityManager(subject);
     }
 
     /**
@@ -103,6 +109,12 @@ public class SubjectRunnable implements Runnable {
      * </pre>
      */
     public void run() {
+        if (ScopedValues.INSTANCE.isSupported()) {
+            ScopedValues.INSTANCE.run(this, runnable, subject, 
securityManager);
+            return;
+        }
+
+        // fallback to ThreadState binding if ScopedValues are not available
         try {
             threadState.bind();
             doRun(this.runnable);
@@ -116,7 +128,7 @@ public class SubjectRunnable implements Runnable {
      *
      * @param runnable the target runnable to run.
      */
-    protected void doRun(Runnable runnable) {
+    public void doRun(Runnable runnable) {
         runnable.run();
     }
 }
diff --git 
a/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java 
b/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java
index 43d47259a..1bad717f8 100644
--- 
a/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java
+++ 
b/core/src/main/java/org/apache/shiro/subject/support/SubjectThreadState.java
@@ -57,15 +57,7 @@ public class SubjectThreadState implements ThreadState {
             throw new IllegalArgumentException("Subject argument cannot be 
null.");
         }
         this.subject = subject;
-
-        SecurityManager securityManager = null;
-        if (subject instanceof DelegatingSubject delegatingSubject) {
-            securityManager = delegatingSubject.getSecurityManager();
-        }
-        if (securityManager == null) {
-            securityManager = ThreadContext.getSecurityManager();
-        }
-        this.securityManager = securityManager;
+        this.securityManager = getSecurityManager(subject);
     }
 
     /**
@@ -121,4 +113,15 @@ public class SubjectThreadState implements ThreadState {
     public void clear() {
         ThreadContext.remove();
     }
+
+    public static SecurityManager getSecurityManager(Subject subject) {
+        SecurityManager securityManager = null;
+        if (subject instanceof DelegatingSubject delegatingSubject) {
+            securityManager = delegatingSubject.getSecurityManager();
+        }
+        if (securityManager == null) {
+            securityManager = ThreadContext.getSecurityManager();
+        }
+        return securityManager;
+    }
 }
diff --git a/core/src/main/java/org/apache/shiro/util/DefaultScopedValues.java 
b/core/src/main/java/org/apache/shiro/util/DefaultScopedValues.java
new file mode 100644
index 000000000..e12ec6bb8
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/DefaultScopedValues.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+/**
+ * Overridden via MR-Jar to provide a Java 25 implementation of {@link 
ScopedValues} when running on Java 25 or later.
+ */
+final class DefaultScopedValues implements ScopedValues {
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java 
b/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java
deleted file mode 100644
index 1735a5b0e..000000000
--- a/core/src/main/java/org/apache/shiro/util/JavaEnvironment.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.shiro.util;
-
-/**
- * Internal helper class used to find the Java/JDK version
- * that Shiro is operating within, to allow for automatically
- * adapting to the present platform's capabilities.
- *
- * <p>Note that Shiro does not support 1.2 or earlier JVMs - only 1.3 and 
later.
- *
- * <p><em>This class was borrowed and heavily based upon a nearly identical 
version found in
- * the <a href="http://www.springframework.org/";>Spring Framework</a>, with 
minor modifications.
- * The original author names and copyright (Apache 2.0) has been left in 
place.  A special
- * thanks to Rod Johnson, Juergen Hoeller, and Rick Evans for making this 
available.</em>
- *
- * @since 0.2
- * @deprecated This class is no longer used in Shiro and will be removed in 
the next major version.
- */
-@Deprecated
-public abstract class JavaEnvironment {
-
-    /**
-     * Constant identifying the 1.3.x JVM (JDK 1.3).
-     */
-    public static final int JAVA_13 = 0;
-
-    /**
-     * Constant identifying the 1.4.x JVM (J2SE 1.4).
-     */
-    public static final int JAVA_14 = 1;
-
-    /**
-     * Constant identifying the 1.5 JVM (Java 5).
-     */
-    public static final int JAVA_15 = 2;
-
-    /**
-     * Constant identifying the 1.6 JVM (Java 6).
-     */
-    public static final int JAVA_16 = 3;
-
-    /**
-     * Constant identifying the 1.7 JVM.
-     */
-    public static final int JAVA_17 = 4;
-
-    /**
-     * Constant identifying the 1.8 JVM.
-     */
-    public static final int JAVA_18 = 5;
-
-    /**
-     * The virtual machine version, i.e. 
<code>System.getProperty("java.version");</code>.
-     */
-    private static final String VERSION;
-
-    /**
-     * The virtual machine <em>major</em> version.  For example, with a 
<code>version</code> of
-     * <code>1.5.6_10</code>, this would be <code>1.5</code>
-     */
-    private static final int MAJOR_VERSION;
-
-    /**
-     * Static code initialization block that sets the
-     * <code>version</code> and <code>majorVersion</code> Class constants
-     * upon initialization.
-     */
-    static {
-        VERSION = System.getProperty("java.version");
-        // version String should look like "1.4.2_10"
-
-// NOTE:   JDK 1.9 will be versioned differently '9' and/or 9.x.x
-// https://blogs.oracle.com/java-platform-group/entry/a_new_jdk_9_version
-
-        if (VERSION.contains("1.8.")) {
-            MAJOR_VERSION = JAVA_18;
-        } else if (VERSION.contains("1.7.")) {
-            MAJOR_VERSION = JAVA_17;
-        } else if (VERSION.contains("1.6.")) {
-            MAJOR_VERSION = JAVA_16;
-        } else if (VERSION.contains("1.5.")) {
-            MAJOR_VERSION = JAVA_15;
-        } else if (VERSION.contains("1.4.")) {
-            MAJOR_VERSION = JAVA_14;
-        } else {
-            // else leave 1.3 as default (it's either 1.3 or unknown)
-            MAJOR_VERSION = JAVA_13;
-        }
-    }
-
-
-    /**
-     * Return the full Java version string, as returned by
-     * <code>System.getProperty("java.version")</code>.
-     *
-     * @return the full Java version string
-     * @see System#getProperty(String)
-     */
-    public static String getVersion() {
-        return VERSION;
-    }
-
-    /**
-     * Get the major version code. This means we can do things like
-     * <code>if (getMajorVersion() &lt; JAVA_14)</code>.
-     *
-     * @return a code comparable to the JAVA_XX codes in this class
-     * @see #JAVA_13
-     * @see #JAVA_14
-     * @see #JAVA_15
-     * @see #JAVA_16
-     * @see #JAVA_17
-     * @see #JAVA_18
-     */
-    public static int getMajorVersion() {
-        return MAJOR_VERSION;
-    }
-
-    /**
-     * Convenience method to determine if the current JVM is at least Java 1.4.
-     *
-     * @return <code>true</code> if the current JVM is at least Java 1.4
-     * @see #getMajorVersion()
-     * @see #JAVA_14
-     * @see #JAVA_15
-     * @see #JAVA_16
-     * @see #JAVA_17
-     * @see #JAVA_18
-     */
-    public static boolean isAtLeastVersion14() {
-        return getMajorVersion() >= JAVA_14;
-    }
-
-    /**
-     * Convenience method to determine if the current JVM is at least
-     * Java 1.5 (Java 5).
-     *
-     * @return <code>true</code> if the current JVM is at least Java 1.5
-     * @see #getMajorVersion()
-     * @see #JAVA_15
-     * @see #JAVA_16
-     * @see #JAVA_17
-     * @see #JAVA_18
-     */
-    public static boolean isAtLeastVersion15() {
-        return getMajorVersion() >= JAVA_15;
-    }
-
-    /**
-     * Convenience method to determine if the current JVM is at least
-     * Java 1.6 (Java 6).
-     *
-     * @return <code>true</code> if the current JVM is at least Java 1.6
-     * @see #getMajorVersion()
-     * @see #JAVA_15
-     * @see #JAVA_16
-     * @see #JAVA_17
-     * @see #JAVA_18
-     * @since 1.2
-     */
-    public static boolean isAtLeastVersion16() {
-        return getMajorVersion() >= JAVA_16;
-    }
-}
diff --git a/core/src/main/java/org/apache/shiro/util/ScopedValues.java 
b/core/src/main/java/org/apache/shiro/util/ScopedValues.java
new file mode 100644
index 000000000..4cbb1107a
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/util/ScopedValues.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectCallable;
+import org.apache.shiro.subject.support.SubjectRunnable;
+import java.util.concurrent.Callable;
+
+/**
+ * An abstraction over Java 25's ScopedValue to allow Shiro to use it when 
running on Java 25 or later,
+ * but degrade gracefully to ThreadLocals when running on older Java versions.
+ * This is used for Subject and Security manager to allow them to be 
propagated across thread boundaries.
+ */
+public interface ScopedValues {
+    record Values(SecurityManager securityManager, Subject subject) { }
+
+    ScopedValues INSTANCE = new DefaultScopedValues();
+
+    boolean isSupported();
+
+    default boolean isBound() {
+        throw new IllegalStateException("ScopedValues are not supported in 
this Java version");
+    }
+
+    default Values get() {
+        throw new IllegalStateException("ScopedValues are not supported in 
this Java version");
+    }
+
+    default  <T> T call(SubjectCallable<T> callable, Callable<T> target,
+                      Subject subject, SecurityManager securityManager) throws 
Exception {
+        throw new IllegalStateException("ScopedValues are not supported in 
this Java version");
+    }
+
+    default void run(SubjectRunnable runnable, Runnable target, Subject 
subject, SecurityManager securityManager) {
+        throw new IllegalStateException("ScopedValues are not supported in 
this Java version");
+    }
+}
diff --git 
a/core/src/main/java25/org/apache/shiro/util/DefaultScopedValues.java 
b/core/src/main/java25/org/apache/shiro/util/DefaultScopedValues.java
new file mode 100644
index 000000000..2514a12bf
--- /dev/null
+++ b/core/src/main/java25/org/apache/shiro/util/DefaultScopedValues.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.util;
+
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.subject.support.SubjectCallable;
+import org.apache.shiro.subject.support.SubjectRunnable;
+import java.util.concurrent.Callable;
+
+/**
+ * Default {@link ScopedValues} implementation that uses Java 25's {@link 
ScopedValue} to store the Subject and SecurityManager
+ */
+final class DefaultScopedValues implements ScopedValues {
+    private static final ScopedValue<Values> VALUES = 
ScopedValue.newInstance();
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public boolean isBound() {
+        return VALUES.isBound();
+    }
+
+    @Override
+    public Values get() {
+        return VALUES.get();
+    }
+
+    @Override
+    public <T> T call(SubjectCallable<T> callable, Callable<T> target,
+                             Subject subject, SecurityManager securityManager) 
throws Exception {
+        return ScopedValue.where(VALUES, new Values(securityManager, subject))
+                .call(() -> callable.doCall(target));
+    }
+
+    @Override
+    public void run(SubjectRunnable runnable, Runnable target, Subject 
subject, SecurityManager securityManager) {
+        ScopedValue.where(VALUES, new Values(securityManager, subject))
+                .run(() -> runnable.doRun(target));
+    }
+}
diff --git 
a/integration-tests/jakarta-ee/src/main/java/org/apache/shiro/testing/jaxrs/WhoamiResource.java
 
b/integration-tests/jakarta-ee/src/main/java/org/apache/shiro/testing/jaxrs/WhoamiResource.java
index 567a4b077..eff0860b9 100644
--- 
a/integration-tests/jakarta-ee/src/main/java/org/apache/shiro/testing/jaxrs/WhoamiResource.java
+++ 
b/integration-tests/jakarta-ee/src/main/java/org/apache/shiro/testing/jaxrs/WhoamiResource.java
@@ -28,6 +28,7 @@ import jakarta.ws.rs.core.Response.Status;
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.lang.ShiroException;
+import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.ThreadContext;
 
 @Path("whoami")
@@ -76,13 +77,20 @@ public class WhoamiResource {
 
     private <T> T check(Supplier<T> happy, Supplier<T> sad, String user, 
String password) {
         try {
-            ThreadContext.bind(testApplication.getSecurityManager());
-            SecurityUtils.getSubject().login(new UsernamePasswordToken(user, 
password));
-            return happy.get();
+            return new Subject.Builder()
+                    .securityManager(testApplication.getSecurityManager())
+                    .buildSubject()
+                    .execute(() -> {
+                        SecurityUtils.getSubject().login(new 
UsernamePasswordToken(user, password));
+                        try {
+                            return happy.get();
+                        } finally {
+                            SecurityUtils.getSubject().logout();
+                        }
+                    });
         } catch (ShiroException e) {
             return sad.get();
         } finally {
-            SecurityUtils.getSubject().logout();
             ThreadContext.unbindSecurityManager();
             ThreadContext.unbindSubject();
             ThreadContext.remove();
diff --git 
a/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java 
b/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
index 0e5b92c88..7f2a46d7d 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/ShiroSessionScope.java
@@ -24,6 +24,7 @@ import com.google.inject.Provider;
 import com.google.inject.Scope;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.subject.Subject;
+import org.apache.shiro.util.ScopedValues;
 import org.apache.shiro.util.ThreadContext;
 
 /**
@@ -33,7 +34,12 @@ public class ShiroSessionScope implements Scope {
     public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) 
{
         return new Provider<T>() {
             public T get() {
-                Subject subject = ThreadContext.getSubject();
+                Subject subject;
+                if (ScopedValues.INSTANCE.isSupported() && 
ScopedValues.INSTANCE.isBound()) {
+                    subject = ScopedValues.INSTANCE.get().subject();
+                } else {
+                    subject = ThreadContext.getSubject();
+                }
                 if (subject == null) {
                     throw new OutOfScopeException("There is no Shiro Session 
currently in scope.");
                 }
diff --git 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
index 0742d25b1..ea5cee73c 100644
--- 
a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
+++ 
b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/ShiroFilter.java
@@ -47,7 +47,6 @@ import lombok.SneakyThrows;
 import lombok.experimental.Delegate;
 import lombok.extern.slf4j.Slf4j;
 import static 
org.apache.shiro.ee.listeners.EnvironmentLoaderListener.isServletNoPrincipal;
-import org.apache.shiro.mgt.DefaultSecurityManager;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.SessionException;
@@ -169,8 +168,7 @@ public class ShiroFilter extends 
org.apache.shiro.web.servlet.ShiroFilter {
 
         @Override
         public Subject createSubject(SubjectContext context) {
-            if (context instanceof WebSubjectContext webContext && wrapped 
instanceof DefaultSecurityManager) {
-                DefaultWebSecurityManager wsm = (DefaultWebSecurityManager) 
wrapped;
+            if (context instanceof WebSubjectContext webContext && wrapped 
instanceof DefaultWebSecurityManager wsm) {
                 Session session = null;
                 try {
                     session = wsm.getSession(new 
WebSessionKey(webContext.getSessionId(), webContext.getServletRequest(),

Reply via email to