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

gnodet pushed a commit to branch context-value-scoped-value-support
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to 
refs/heads/context-value-scoped-value-support by this push:
     new ede2fff4bb33 Extract ThreadLocalContextValue, fix concurrentConsumers 
default, fix docs
ede2fff4bb33 is described below

commit ede2fff4bb33531e0ae01e739350f0fc08da9ba9
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Mar 4 09:37:43 2026 +0100

    Extract ThreadLocalContextValue, fix concurrentConsumers default, fix docs
    
    - Extract ThreadLocalContextValue from inner class (duplicated in base
      and JDK 25 ContextValueFactory) into a shared top-level class
    - Default concurrentConsumers to 0 (unlimited) when virtualThreadPerTask
      is enabled, since the traditional default of 1 defeats the purpose
    - Fix Example 3 in virtual-threads.adoc: ContextValue doesn't propagate
      across SEDA boundaries, use exchange properties instead
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../apache/camel/component/seda/SedaEndpoint.java  |  6 ++
 .../camel/util/concurrent/ContextValueFactory.java | 53 ---------------
 .../util/concurrent/ThreadLocalContextValue.java   | 76 ++++++++++++++++++++++
 .../camel/util/concurrent/ContextValueFactory.java | 53 ---------------
 .../modules/ROOT/pages/virtual-threads.adoc        | 17 ++---
 5 files changed, 91 insertions(+), 114 deletions(-)

diff --git 
a/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java
 
b/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java
index 5385748e8fb7..8b5cc372819c 100644
--- 
a/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java
+++ 
b/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java
@@ -630,6 +630,12 @@ public class SedaEndpoint extends DefaultEndpoint 
implements AsyncEndpoint, Brow
     protected void doInit() throws Exception {
         super.doInit();
 
+        // When virtualThreadPerTask is enabled, default concurrentConsumers 
to 0 (unlimited)
+        // since the traditional default of 1 defeats the purpose of 
thread-per-task mode
+        if (virtualThreadPerTask && concurrentConsumers == 1) {
+            concurrentConsumers = 0;
+        }
+
         if (discardWhenFull && blockWhenFull) {
             throw new IllegalArgumentException(
                     "Cannot enable both discardWhenFull=true and 
blockWhenFull=true."
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ContextValueFactory.java
 
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ContextValueFactory.java
index f3b991c29158..7580d73fcd12 100644
--- 
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ContextValueFactory.java
+++ 
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ContextValueFactory.java
@@ -84,57 +84,4 @@ class ContextValueFactory {
         });
     }
 
-    /**
-     * ThreadLocal-based implementation of ContextValue.
-     */
-    static class ThreadLocalContextValue<T> implements ContextValue<T> {
-        private final String name;
-        private final ThreadLocal<T> threadLocal;
-
-        ThreadLocalContextValue(String name) {
-            this.name = name;
-            this.threadLocal = new ThreadLocal<>();
-        }
-
-        ThreadLocalContextValue(String name, Supplier<T> supplier) {
-            this.name = name;
-            this.threadLocal = ThreadLocal.withInitial(supplier);
-        }
-
-        @Override
-        public T get() {
-            return threadLocal.get();
-        }
-
-        @Override
-        public T orElse(T defaultValue) {
-            T value = threadLocal.get();
-            return value != null ? value : defaultValue;
-        }
-
-        @Override
-        public boolean isBound() {
-            return threadLocal.get() != null;
-        }
-
-        @Override
-        public void set(T value) {
-            threadLocal.set(value);
-        }
-
-        @Override
-        public void remove() {
-            threadLocal.remove();
-        }
-
-        @Override
-        public String name() {
-            return name;
-        }
-
-        @Override
-        public String toString() {
-            return "ContextValue[" + name + "]";
-        }
-    }
 }
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadLocalContextValue.java
 
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadLocalContextValue.java
new file mode 100644
index 000000000000..f2bffdd04d94
--- /dev/null
+++ 
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadLocalContextValue.java
@@ -0,0 +1,76 @@
+/*
+ * 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.camel.util.concurrent;
+
+import java.util.function.Supplier;
+
+/**
+ * ThreadLocal-based implementation of {@link ContextValue}.
+ * <p>
+ * This is the default implementation used on JDK 17-24, and also used on JDK 
25+ when virtual threads are not enabled
+ * or when a ThreadLocal is explicitly requested via {@link 
ContextValue#newThreadLocal(String)}.
+ */
+class ThreadLocalContextValue<T> implements ContextValue<T> {
+    private final String name;
+    private final ThreadLocal<T> threadLocal;
+
+    ThreadLocalContextValue(String name) {
+        this.name = name;
+        this.threadLocal = new ThreadLocal<>();
+    }
+
+    ThreadLocalContextValue(String name, Supplier<T> supplier) {
+        this.name = name;
+        this.threadLocal = ThreadLocal.withInitial(supplier);
+    }
+
+    @Override
+    public T get() {
+        return threadLocal.get();
+    }
+
+    @Override
+    public T orElse(T defaultValue) {
+        T value = threadLocal.get();
+        return value != null ? value : defaultValue;
+    }
+
+    @Override
+    public boolean isBound() {
+        return threadLocal.get() != null;
+    }
+
+    @Override
+    public void set(T value) {
+        threadLocal.set(value);
+    }
+
+    @Override
+    public void remove() {
+        threadLocal.remove();
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return "ContextValue[" + name + "]";
+    }
+}
diff --git 
a/core/camel-util/src/main/java25/org/apache/camel/util/concurrent/ContextValueFactory.java
 
b/core/camel-util/src/main/java25/org/apache/camel/util/concurrent/ContextValueFactory.java
index 8575a1f91705..b8b835d295df 100644
--- 
a/core/camel-util/src/main/java25/org/apache/camel/util/concurrent/ContextValueFactory.java
+++ 
b/core/camel-util/src/main/java25/org/apache/camel/util/concurrent/ContextValueFactory.java
@@ -163,57 +163,4 @@ class ContextValueFactory {
         }
     }
 
-    /**
-     * ThreadLocal-based implementation of ContextValue.
-     */
-    static class ThreadLocalContextValue<T> implements ContextValue<T> {
-        private final String name;
-        private final ThreadLocal<T> threadLocal;
-
-        ThreadLocalContextValue(String name) {
-            this.name = name;
-            this.threadLocal = new ThreadLocal<>();
-        }
-
-        ThreadLocalContextValue(String name, Supplier<T> supplier) {
-            this.name = name;
-            this.threadLocal = ThreadLocal.withInitial(supplier);
-        }
-
-        @Override
-        public T get() {
-            return threadLocal.get();
-        }
-
-        @Override
-        public T orElse(T defaultValue) {
-            T value = threadLocal.get();
-            return value != null ? value : defaultValue;
-        }
-
-        @Override
-        public boolean isBound() {
-            return threadLocal.get() != null;
-        }
-
-        @Override
-        public void set(T value) {
-            threadLocal.set(value);
-        }
-
-        @Override
-        public void remove() {
-            threadLocal.remove();
-        }
-
-        @Override
-        public String name() {
-            return name;
-        }
-
-        @Override
-        public String toString() {
-            return "ContextValue[" + name + ",ThreadLocal]";
-        }
-    }
 }
diff --git a/docs/user-manual/modules/ROOT/pages/virtual-threads.adoc 
b/docs/user-manual/modules/ROOT/pages/virtual-threads.adoc
index 3a97a1500055..b86c0916f221 100644
--- a/docs/user-manual/modules/ROOT/pages/virtual-threads.adoc
+++ b/docs/user-manual/modules/ROOT/pages/virtual-threads.adoc
@@ -801,7 +801,7 @@ public class ParallelEnrichmentRoute extends RouteBuilder {
 }
 ----
 
-=== Example 3: Context Propagation Across Routes
+=== Example 3: Context Propagation Within a Route
 
 [source,java]
 ----
@@ -811,21 +811,20 @@ public class TenantAwareRoute extends RouteBuilder {
 
     @Override
     public void configure() {
+        // ContextValue is scoped to the current thread - it works within a 
single
+        // route or call chain, not across asynchronous boundaries like SEDA 
queues.
+        // For cross-route context, use exchange properties instead.
         from("platform-http:/api/{tenant}/orders")
             .process(exchange -> {
                 String tenant = exchange.getMessage().getHeader("tenant", 
String.class);
-                // Bind tenant for the entire processing scope
-                ContextValue.where(TENANT_ID, tenant, () -> {
-                    exchange.setProperty("tenantId", tenant);
-                    return null;
-                });
+                exchange.setProperty("tenantId", tenant);
             })
             .to("seda:process?virtualThreadPerTask=true");
 
         from("seda:process?virtualThreadPerTask=true&concurrentConsumers=500")
             .process(exchange -> {
-                // Access tenant ID in any processor
-                String tenant = TENANT_ID.orElse("default");
+                // Use exchange properties for context that crosses async 
boundaries
+                String tenant = exchange.getProperty("tenantId", "default", 
String.class);
                 log.info("Processing for tenant: {}", tenant);
             })
             .toD("jpa:Order?persistenceUnit=${exchangeProperty.tenantId}");
@@ -833,6 +832,8 @@ public class TenantAwareRoute extends RouteBuilder {
 }
 ----
 
+NOTE: `ContextValue` is scoped to the current thread (or `ScopedValue` scope 
on JDK 25+). It does *not* propagate across asynchronous boundaries like SEDA 
queues. For data that needs to cross route boundaries, use exchange properties 
or headers. `ContextValue` is designed for propagating context *within* a 
synchronous call chain (e.g., during route creation or processor 
initialization).
+
 == Summary
 
 Virtual threads in Apache Camel provide:

Reply via email to