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

gnodet pushed a commit to branch 
CAMEL-23225-otel-context-propagation-task-decorator
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git

commit d7ea516a779247a613a9b3c3fee303d7b9d5d273
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Mar 25 07:57:43 2026 +0100

    CAMEL-23225: Propagate OTel context in Spring Boot task executors
    
    Register an OTel context-propagating TaskDecorator bean in the
    camel-opentelemetry2-starter auto-configuration. This ensures
    Spring's ThreadPoolTaskExecutor (used by camel-platform-http-starter)
    propagates OpenTelemetry context to worker threads, preventing
    context leaks in async processing scenarios.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../camel-opentelemetry2-starter/pom.xml           | 12 ++++
 .../starter/OpenTelemetry2AutoConfiguration.java   | 16 +++++
 .../OtelContextPropagatingTaskDecoratorTest.java   | 78 ++++++++++++++++++++++
 3 files changed, 106 insertions(+)

diff --git a/components-starter/camel-opentelemetry2-starter/pom.xml 
b/components-starter/camel-opentelemetry2-starter/pom.xml
index 451f403831f..ae6e903f8be 100644
--- a/components-starter/camel-opentelemetry2-starter/pom.xml
+++ b/components-starter/camel-opentelemetry2-starter/pom.xml
@@ -39,6 +39,18 @@
       <artifactId>camel-opentelemetry2</artifactId>
       <version>${camel-version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
+      <version>${spring-boot-version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-test-spring-junit6</artifactId>
+      <version>${camel-version}</version>
+      <scope>test</scope>
+    </dependency>
     <!--START OF GENERATED CODE-->
     <dependency>
       <groupId>org.apache.camel.springboot</groupId>
diff --git 
a/components-starter/camel-opentelemetry2-starter/src/main/java/org/apache/camel/opentelemetry2/starter/OpenTelemetry2AutoConfiguration.java
 
b/components-starter/camel-opentelemetry2-starter/src/main/java/org/apache/camel/opentelemetry2/starter/OpenTelemetry2AutoConfiguration.java
index 17f79d9de6a..159e0604f48 100644
--- 
a/components-starter/camel-opentelemetry2-starter/src/main/java/org/apache/camel/opentelemetry2/starter/OpenTelemetry2AutoConfiguration.java
+++ 
b/components-starter/camel-opentelemetry2-starter/src/main/java/org/apache/camel/opentelemetry2/starter/OpenTelemetry2AutoConfiguration.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.opentelemetry2.starter;
 
+import io.opentelemetry.context.Context;
+
 import org.apache.camel.CamelContext;
 import org.apache.camel.opentelemetry2.OpenTelemetryTracer;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -23,6 +25,7 @@ import 
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import 
org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.TaskDecorator;
 
 @Configuration(proxyBeanMethods = false)
 @EnableConfigurationProperties(OpenTelemetry2ConfigurationProperties.class)
@@ -45,4 +48,17 @@ public class OpenTelemetry2AutoConfiguration {
 
         return ottracer;
     }
+
+    @Bean
+    @ConditionalOnMissingBean(TaskDecorator.class)
+    TaskDecorator otelContextPropagatingTaskDecorator() {
+        return runnable -> {
+            Context context = Context.current();
+            return () -> {
+                try (var ignored = context.makeCurrent()) {
+                    runnable.run();
+                }
+            };
+        };
+    }
 }
diff --git 
a/components-starter/camel-opentelemetry2-starter/src/test/java/org/apache/camel/opentelemetry2/starter/OtelContextPropagatingTaskDecoratorTest.java
 
b/components-starter/camel-opentelemetry2-starter/src/test/java/org/apache/camel/opentelemetry2/starter/OtelContextPropagatingTaskDecoratorTest.java
new file mode 100644
index 00000000000..7e128c719a8
--- /dev/null
+++ 
b/components-starter/camel-opentelemetry2-starter/src/test/java/org/apache/camel/opentelemetry2/starter/OtelContextPropagatingTaskDecoratorTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.opentelemetry2.starter;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.ContextKey;
+
+import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.task.TaskDecorator;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@CamelSpringBootTest
+@EnableAutoConfiguration
+@SpringBootTest(classes = OtelContextPropagatingTaskDecoratorTest.class)
+public class OtelContextPropagatingTaskDecoratorTest {
+
+    private static final ContextKey<String> TEST_KEY = 
ContextKey.named("test-key");
+
+    @Autowired
+    private TaskDecorator taskDecorator;
+
+    @Test
+    void taskDecoratorBeanIsRegistered() {
+        assertNotNull(taskDecorator);
+    }
+
+    @Test
+    void taskDecoratorPropagatesOtelContext() throws Exception {
+        AtomicReference<String> valueOnWorkerThread = new AtomicReference<>();
+        CountDownLatch latch = new CountDownLatch(1);
+
+        // Set a value in the OTel context on the current thread
+        Context contextWithValue = Context.current().with(TEST_KEY, 
"propagated-value");
+        try (var ignored = contextWithValue.makeCurrent()) {
+            // Decorate the runnable while the context is active
+            Runnable decorated = taskDecorator.decorate(() -> {
+                valueOnWorkerThread.set(Context.current().get(TEST_KEY));
+                latch.countDown();
+            });
+
+            // Run on a different thread — context should still be propagated
+            ExecutorService executor = Executors.newSingleThreadExecutor();
+            try {
+                executor.execute(decorated);
+                latch.await();
+            } finally {
+                executor.shutdown();
+            }
+        }
+
+        assertEquals("propagated-value", valueOnWorkerThread.get());
+    }
+}

Reply via email to