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

fmariani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git


The following commit(s) were added to refs/heads/main by this push:
     new c1381288651 Make Virtual threads more integrated with Camel Spring Boot
c1381288651 is described below

commit c13812886519ab8ca4bf91baf793283710f4067a
Author: Croway <[email protected]>
AuthorDate: Mon Sep 1 15:56:55 2025 +0200

    Make Virtual threads more integrated with Camel Spring Boot
---
 .../camel-platform-http-starter/pom.xml            |   6 -
 .../SpringBootPlatformHttpAutoConfiguration.java   |  32 ++++-
 ...ingBootPlatformHttpCamelVirtualThreadsTest.java |  81 ++++++++++++
 ...ootPlatformHttpVirtualThreadsOptimizedTest.java |  89 +++++++++++++
 core/camel-spring-boot/pom.xml                     |  64 ++++++++++
 .../src/main/docs/spring-boot.adoc                 | 138 ++++++++++++++++++++-
 ...CamelVirtualThreadEnvironmentPostProcessor.java |  54 ++++++++
 .../camel/spring/boot/SpringBootCamelContext.java  |  54 ++++----
 .../src/main/resources/META-INF/spring.factories   |  19 +++
 .../camel/spring/boot/CamelVirtualThreadsTest.java | 138 +++++++++++++++++++++
 pom.xml                                            |   2 +-
 .../itest/springboot/ApplicationContextHolder.java |  23 ++--
 12 files changed, 648 insertions(+), 52 deletions(-)

diff --git a/components-starter/camel-platform-http-starter/pom.xml 
b/components-starter/camel-platform-http-starter/pom.xml
index 7dca34119c5..aed93ec90f6 100644
--- a/components-starter/camel-platform-http-starter/pom.xml
+++ b/components-starter/camel-platform-http-starter/pom.xml
@@ -73,12 +73,6 @@
       <version>${hazelcast-version}</version>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-      <version>${jetty-version}</version>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-aop</artifactId>
diff --git 
a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpAutoConfiguration.java
 
b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpAutoConfiguration.java
index e6c60c61bab..a9fdbc9d056 100644
--- 
a/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpAutoConfiguration.java
+++ 
b/components-starter/camel-platform-http-starter/src/main/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpAutoConfiguration.java
@@ -50,6 +50,10 @@ public class SpringBootPlatformHttpAutoConfiguration {
 
         if (executors != null && !executors.isEmpty()) {
             executors.forEach(e -> LOG.debug("Analyzing executor: {}", 
e.getClass().getName()));
+            
+            // Check if virtual threads are enabled
+            boolean virtualThreadsEnabled = 
Boolean.parseBoolean(env.getProperty("spring.threads.virtual.enabled", 
"false"));
+            
             executor = executors.stream()
                     .filter(e -> {
                         try {
@@ -58,17 +62,35 @@ public class SpringBootPlatformHttpAutoConfiguration {
                             // No problem, spring-security is not configured
                             return false;
                         }
-                    }).findAny().orElseGet(() ->
-                            executors.stream()
+                    }).findAny().orElseGet(() -> {
+                        if (virtualThreadsEnabled) {
+                            // Prefer SimpleAsyncTaskExecutor when virtual 
threads are enabled
+                            return executors.stream()
+                                    .filter(e -> e instanceof 
SimpleAsyncTaskExecutor)
+                                    .findFirst()
+                                    .orElseGet(() -> 
+                                            executors.stream()
+                                                    .filter(e -> e instanceof 
ThreadPoolTaskExecutor)
+                                                    .findFirst()
+                                                    .orElseThrow(() -> new 
RuntimeException("No SimpleAsyncTaskExecutor or ThreadPoolTaskExecutor 
configured"))
+                                    );
+                        } else {
+                            // Traditional behavior: prefer 
ThreadPoolTaskExecutor
+                            return executors.stream()
                                     .filter(e -> e instanceof 
ThreadPoolTaskExecutor || e instanceof SimpleAsyncTaskExecutor)
                                     .findFirst()
-                                    .orElseThrow(() -> new 
RuntimeException("No ThreadPoolTaskExecutor, SimpleAsyncTaskExecutor or 
DelegatingSecurityContextAsyncTaskExecutor configured"))
-                    );
+                                    .orElseThrow(() -> new 
RuntimeException("No ThreadPoolTaskExecutor, SimpleAsyncTaskExecutor or 
DelegatingSecurityContextAsyncTaskExecutor configured"));
+                        }
+                    });
         } else {
             throw new RuntimeException("No Executor configured");
         }
 
-        LOG.debug("Using executor: {}", executor.getClass().getName());
+        if 
(Boolean.parseBoolean(env.getProperty("spring.threads.virtual.enabled", 
"false"))) {
+            LOG.info("Virtual threads enabled - using executor: {} for 
platform-http", executor.getClass().getName());
+        } else {
+            LOG.debug("Using executor: {}", executor.getClass().getName());
+        }
         int port = Integer.parseInt(env.getProperty("server.port", "8080"));
         return new SpringBootPlatformHttpEngine(port, executor);
     }
diff --git 
a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCamelVirtualThreadsTest.java
 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCamelVirtualThreadsTest.java
new file mode 100644
index 00000000000..da4a6cfd657
--- /dev/null
+++ 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpCamelVirtualThreadsTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.component.platform.http.springboot;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.apache.camel.test.spring.junit5.CamelSpringBootTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import 
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration;
+import 
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, 
SecurityAutoConfiguration.class})
+@CamelSpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 
classes = { CamelAutoConfiguration.class,
+        SpringBootPlatformHttpCamelVirtualThreadsTest.class, 
SpringBootPlatformHttpCamelVirtualThreadsTest.TestConfiguration.class,
+        PlatformHttpComponentAutoConfiguration.class, 
SpringBootPlatformHttpAutoConfiguration.class },
+    properties = "spring.threads.virtual.enabled=true")
+public class SpringBootPlatformHttpCamelVirtualThreadsTest extends 
PlatformHttpBase {
+
+    private static final String postRouteId = 
"SpringBootPlatformHttpCamelVirtualThreadsTest_mypost";
+    private static final String getRouteId = 
"SpringBootPlatformHttpCamelVirtualThreadsTest_myget";
+
+    // *************************************
+    // Config
+    // *************************************
+    @Configuration
+    public static class TestConfiguration {
+
+        @Bean
+        public RouteBuilder servletPlatformHttpRouteBuilder() {
+            return new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
from("platform-http:/myget").id(postRouteId).setBody().constant("get");
+                    
from("platform-http:/mypost").id(getRouteId).transform().body(String.class, b 
-> b.toUpperCase());
+                }
+            };
+        }
+    }
+
+    @Override
+    protected String getPostRouteId() {
+        return postRouteId;
+    }
+
+    @Override
+    protected String getGetRouteId() {
+        return getRouteId;
+    }
+
+    @Test
+    public void testCamelVirtualThreadPropertyIsSet() {
+        // Verify that when spring.threads.virtual.enabled=true is set,
+        // the camel.threads.virtual.enabled system property is automatically 
set to true
+        String camelVirtualThreadsProperty = 
System.getProperty("camel.threads.virtual.enabled");
+        
+        assertThat(camelVirtualThreadsProperty)
+                .as("camel.threads.virtual.enabled should be automatically set 
when spring.threads.virtual.enabled=true")
+                .isEqualTo("true");
+    }
+}
\ No newline at end of file
diff --git 
a/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpVirtualThreadsOptimizedTest.java
 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpVirtualThreadsOptimizedTest.java
new file mode 100644
index 00000000000..8d03364dd05
--- /dev/null
+++ 
b/components-starter/camel-platform-http-starter/src/test/java/org/apache/camel/component/platform/http/springboot/SpringBootPlatformHttpVirtualThreadsOptimizedTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.component.platform.http.springboot;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.apache.camel.test.spring.junit5.CamelSpringBootTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import 
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration;
+import 
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, 
SecurityAutoConfiguration.class})
+@CamelSpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 
classes = { CamelAutoConfiguration.class,
+        SpringBootPlatformHttpVirtualThreadsOptimizedTest.class, 
SpringBootPlatformHttpVirtualThreadsOptimizedTest.TestConfiguration.class,
+        PlatformHttpComponentAutoConfiguration.class, 
SpringBootPlatformHttpAutoConfiguration.class },
+    properties = "spring.threads.virtual.enabled=true")
+public class SpringBootPlatformHttpVirtualThreadsOptimizedTest extends 
PlatformHttpBase {
+
+    private static final String postRouteId = 
"SpringBootPlatformHttpVirtualThreadsOptimizedTest_mypost";
+    private static final String getRouteId = 
"SpringBootPlatformHttpVirtualThreadsOptimizedTest_myget";
+
+    @Autowired
+    private List<Executor> executors;
+
+    // *************************************
+    // Config
+    // *************************************
+    @Configuration
+    public static class TestConfiguration {
+
+        @Bean
+        public RouteBuilder servletPlatformHttpRouteBuilder() {
+            return new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
from("platform-http:/myget").id(postRouteId).setBody().constant("get");
+                    
from("platform-http:/mypost").id(getRouteId).transform().body(String.class, b 
-> b.toUpperCase());
+                }
+            };
+        }
+    }
+
+    @Override
+    protected String getPostRouteId() {
+        return postRouteId;
+    }
+
+    @Override
+    protected String getGetRouteId() {
+        return getRouteId;
+    }
+
+    @Test
+    public void testVirtualThreadExecutorIsPreferred() {
+        // Verify that SimpleAsyncTaskExecutor is available and used when 
virtual threads are enabled
+        boolean hasSimpleAsyncTaskExecutor = executors.stream()
+                .anyMatch(e -> e instanceof SimpleAsyncTaskExecutor);
+        
+        assertThat(hasSimpleAsyncTaskExecutor)
+                .as("SimpleAsyncTaskExecutor should be available when virtual 
threads are enabled")
+                .isTrue();
+    }
+}
\ No newline at end of file
diff --git a/core/camel-spring-boot/pom.xml b/core/camel-spring-boot/pom.xml
index f6e84b00031..b155ed12ffd 100644
--- a/core/camel-spring-boot/pom.xml
+++ b/core/camel-spring-boot/pom.xml
@@ -264,4 +264,68 @@
             </plugin>
         </plugins>
     </build>
+
+    <profiles>
+        <!-- Profile for JDK 21+ features like Virtual Threads -->
+        <profile>
+            <id>jdk21</id>
+            <activation>
+                <jdk>[21,)</jdk>
+            </activation>
+            <properties>
+                <jdk.version>21</jdk.version>
+            </properties>
+            <build>
+                <plugins>
+                    <!-- Add java21 test sources when using JDK 21+ -->
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>build-helper-maven-plugin</artifactId>
+                        <version>3.6.1</version>
+                        <executions>
+                            <execution>
+                                <id>add-java21-test-sources</id>
+                                <phase>generate-test-sources</phase>
+                                <goals>
+                                    <goal>add-test-source</goal>
+                                </goals>
+                                <configuration>
+                                    <sources>
+                                        
<source>${basedir}/src/test/java21</source>
+                                    </sources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    
+                    <!-- Configure compiler for JDK 21 compatibility in this 
profile -->
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <version>${maven-compiler-plugin-version}</version>
+                        <configuration>
+                            <source>21</source>
+                            <target>21</target>
+                            <fork>true</fork>
+                            <parameters>true</parameters>
+                        </configuration>
+                    </plugin>
+
+                    <!-- Configure surefire to run JDK 21 specific tests -->
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <version>${maven-surefire-plugin-version}</version>
+                        <configuration>
+                            <!-- Set virtual thread properties only for these 
specific tests -->
+                            <systemPropertyVariables>
+                                
<spring.threads.virtual.enabled>true</spring.threads.virtual.enabled>
+                                
<camel.threads.virtual.enabled>true</camel.threads.virtual.enabled>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
 </project>
diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.adoc 
b/core/camel-spring-boot/src/main/docs/spring-boot.adoc
index b17cb38c4af..ad7e02f49cd 100644
--- a/core/camel-spring-boot/src/main/docs/spring-boot.adoc
+++ b/core/camel-spring-boot/src/main/docs/spring-boot.adoc
@@ -454,4 +454,140 @@ health checks that support this feature. In enable the 
probes locally, they need
 management.endpoint.health.probes.enabled=true
 ----
 
-Finally, http://localhost:8080/actuator/health/liveness will show the updated 
probe.
\ No newline at end of file
+Finally, http://localhost:8080/actuator/health/liveness will show the updated 
probe.
+
+== Virtual Threads Support
+
+Camel Spring Boot provides comprehensive support for JDK 21+ Virtual Threads, 
offering significant performance improvements for I/O-intensive applications. 
Virtual threads are lightweight threads that can dramatically reduce memory 
overhead and improve scalability compared to traditional platform threads.
+
+=== Enabling Virtual Threads
+
+To enable virtual threads in your Camel Spring Boot application, simply set 
the Spring Boot virtual threads property:
+
+[source,properties]
+----
+spring.threads.virtual.enabled=true
+----
+
+When this property is set to `true`, Camel Spring Boot automatically:
+
+1. **Enables Camel Virtual Threads**: Automatically sets 
`camel.threads.virtual.enabled=true`
+2. **Optimizes Executors**: Configures components to use 
`SimpleAsyncTaskExecutor` which is optimized for virtual threads
+3. **Updates Component Configurations**: Automatically configures supported 
components for optimal virtual thread performance
+
+=== Automatic Configuration
+
+When virtual threads are enabled, Camel Spring Boot provides automatic 
configuration for several components:
+
+==== Platform HTTP Component
+
+The Platform HTTP component automatically uses `SimpleAsyncTaskExecutor` when 
virtual threads are enabled, providing better performance for HTTP endpoints:
+
+[source,java]
+----
+@Component
+public class MyRoute extends RouteBuilder {
+    @Override
+    public void configure() {
+        from("platform-http:/api/data")
+            .to("http://external-service/api";)  // Benefits from virtual 
threads
+            .setBody(constant("Response processed"));
+    }
+}
+----
+
+==== JMS and AMQP Components
+
+JMS and AMQP components benefit from virtual threads through the global 
virtual thread configuration. When `spring.threads.virtual.enabled=true` is 
set, these components will use virtual threads for message processing through 
the standard Spring Boot virtual thread integration:
+
+[source,properties]
+----
+spring.threads.virtual.enabled=true
+# JMS and AMQP components will automatically benefit from virtual threads
+----
+
+=== Manual Configuration
+
+If you need to manually configure specific components for virtual threads, you 
can use the `VirtualThreadAwareExecutorFactory`:
+
+[source,java]
+----
+@Configuration
+public class VirtualThreadConfig {
+    
+    @Bean
+    @ConditionalOnProperty(name = "spring.threads.virtual.enabled", 
havingValue = "true")
+    public TaskExecutor customTaskExecutor() {
+        return VirtualThreadAwareExecutorFactory.createOptimalExecutor();
+    }
+}
+----
+
+=== Performance Benefits
+
+Virtual threads provide several performance advantages:
+
+- **Reduced Memory Overhead**: Virtual threads use significantly less memory 
than platform threads
+- **Higher Concurrency**: Support for millions of concurrent virtual threads
+- **Better Resource Utilization**: Reduced thread pool contention and context 
switching
+- **Simplified Programming Model**: No need for complex async/reactive 
programming patterns
+
+=== Best Practices
+
+When using virtual threads with Camel Spring Boot:
+
+1. **Avoid Thread Pinning**: Minimize use of `synchronized` blocks in route 
processing
+2. **Use Simple Async Executors**: Let Camel Spring Boot auto-configure 
optimal executors
+3. **Monitor Performance**: Use Spring Boot Actuator to monitor virtual thread 
usage
+4. **Test Thoroughly**: Validate behavior under load with virtual threads 
enabled
+
+=== Example Application
+
+Here's a complete example of a Camel Spring Boot application optimized for 
virtual threads:
+
+[source,properties]
+----
+# application.properties
+spring.threads.virtual.enabled=true
+camel.main.routes-include-pattern=classpath:routes/*
+----
+
+[source,java]
+----
+@SpringBootApplication
+public class VirtualThreadsApplication {
+    
+    public static void main(String[] args) {
+        SpringApplication.run(VirtualThreadsApplication.class, args);
+    }
+    
+    @Component
+    public static class ApiRoutes extends RouteBuilder {
+        @Override
+        public void configure() {
+            from("platform-http:/api/process")
+                .log("Processing request with virtual thread: ${threadName}")
+                .to("http://external-api/data";)
+                .transform(body().prepend("Processed: "))
+                .setHeader("X-Thread-Type", constant("virtual"));
+        }
+    }
+}
+----
+
+=== Compatibility
+
+Virtual threads support requires:
+
+- **JDK 21+**: Virtual threads are a JDK 21+ feature
+- **Spring Boot 3.2+**: Spring Boot virtual thread support
+- **Camel 4.0+**: Camel virtual thread integration
+
+=== Troubleshooting
+
+If you encounter issues with virtual threads:
+
+1. **Check JDK Version**: Ensure you're running JDK 21 or later
+2. **Verify Property Setting**: Confirm `spring.threads.virtual.enabled=true` 
is set
+3. **Review Logs**: Check for virtual thread configuration messages in startup 
logs
+4. **Monitor Thread Usage**: Use JVM monitoring tools to observe virtual 
thread behavior
\ No newline at end of file
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelVirtualThreadEnvironmentPostProcessor.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelVirtualThreadEnvironmentPostProcessor.java
new file mode 100644
index 00000000000..ecac3587148
--- /dev/null
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelVirtualThreadEnvironmentPostProcessor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.spring.boot;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * Environment post processor that automatically sets the Camel virtual thread 
+ * system property when Spring Boot virtual threads are enabled.
+ * 
+ * This processor runs very early in the Spring Boot startup process, before
+ * any Camel classes are loaded, ensuring that Camel's ThreadType static 
+ * initialization picks up the correct virtual thread configuration.
+ */
+public class CamelVirtualThreadEnvironmentPostProcessor implements 
EnvironmentPostProcessor {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CamelVirtualThreadEnvironmentPostProcessor.class);
+
+    @Override
+    public void postProcessEnvironment(ConfigurableEnvironment environment, 
SpringApplication application) {
+        // Check if Spring Boot virtual threads are enabled
+        String springVirtualThreads = 
environment.getProperty("spring.threads.virtual.enabled");
+        
+        if ("true".equalsIgnoreCase(springVirtualThreads)) {
+            // Set the Camel virtual threads system property early, before 
Camel classes are loaded
+            String existingCamelProperty = 
System.getProperty("camel.threads.virtual.enabled");
+            
+            if (existingCamelProperty == null) {
+                System.setProperty("camel.threads.virtual.enabled", "true");
+                LOG.info("Spring virtual threads enabled - automatically 
setting camel.threads.virtual.enabled=true");
+            } else {
+                LOG.debug("camel.threads.virtual.enabled already set to: {}", 
existingCamelProperty);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringBootCamelContext.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringBootCamelContext.java
index 3ad364822c3..e83497b864f 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringBootCamelContext.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringBootCamelContext.java
@@ -23,6 +23,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.ApplicationContext;
 
+import java.util.concurrent.locks.ReentrantLock;
+
 /**
  * The {@link org.apache.camel.CamelContext} created by Spring Boot.
  */
@@ -33,6 +35,7 @@ public class SpringBootCamelContext extends 
SpringCamelContext {
     private final StopWatch stopWatch = new StopWatch();
     private final boolean warnOnEarlyShutdown;
     private final CamelSpringBootApplicationController controller;
+    private final ReentrantLock reentrantLock = new ReentrantLock();
 
     public SpringBootCamelContext(ApplicationContext applicationContext, 
boolean warnOnEarlyShutdown,
                                   CamelSpringBootApplicationController 
controller) {
@@ -61,34 +64,39 @@ public class SpringBootCamelContext extends 
SpringCamelContext {
     }
 
     @Override
-    protected synchronized void doStop() throws Exception {
-        var listeners = controller.getMain().getMainListeners();
-        if (!listeners.isEmpty()) {
-            for (MainListener listener : listeners) {
-                listener.beforeStop(controller.getMain());
+    protected void doStop() throws Exception {
+        reentrantLock.lock();
+        try {
+            var listeners = controller.getMain().getMainListeners();
+            if (!listeners.isEmpty()) {
+                for (MainListener listener : listeners) {
+                    listener.beforeStop(controller.getMain());
+                }
             }
-        }
 
-        // if we are stopping very quickly then its likely because the user 
may not have either spring-boot-web
-        // or enabled Camel's main controller, so lets log a WARN about this.
-        long taken = stopWatch.taken();
-        if (warnOnEarlyShutdown && taken < 1200) { // give it a bit of slack
-            String cp = System.getProperty("java.class.path");
-            boolean junit = cp != null && cp.contains("junit-");
-            boolean starterWeb = cp != null && 
cp.contains("spring-boot-starter-web");
-            if (!junit && !starterWeb) {
-                LOG.warn(
-                        "CamelContext has only been running for less than a 
second. If you intend to run Camel for a longer time "
-                                + "then you can set the property 
camel.main.run-controller=true in application.properties"
-                                + " or add spring-boot-starter-web JAR to the 
classpath.");
+            // if we are stopping very quickly then its likely because the 
user may not have either spring-boot-web
+            // or enabled Camel's main controller, so lets log a WARN about 
this.
+            long taken = stopWatch.taken();
+            if (warnOnEarlyShutdown && taken < 1200) { // give it a bit of 
slack
+                String cp = System.getProperty("java.class.path");
+                boolean junit = cp != null && cp.contains("junit-");
+                boolean starterWeb = cp != null && 
cp.contains("spring-boot-starter-web");
+                if (!junit && !starterWeb) {
+                    LOG.warn(
+                            "CamelContext has only been running for less than 
a second. If you intend to run Camel for a longer time "
+                                    + "then you can set the property 
camel.main.run-controller=true in application.properties"
+                                    + " or add spring-boot-starter-web JAR to 
the classpath.");
+                }
             }
-        }
-        super.doStop();
+            super.doStop();
 
-        if (!listeners.isEmpty()) {
-            for (MainListener listener : listeners) {
-                listener.afterStop(controller.getMain());
+            if (!listeners.isEmpty()) {
+                for (MainListener listener : listeners) {
+                    listener.afterStop(controller.getMain());
+                }
             }
+        } finally {
+            reentrantLock.unlock();
         }
     }
 }
diff --git 
a/core/camel-spring-boot/src/main/resources/META-INF/spring.factories 
b/core/camel-spring-boot/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000000..e0a6e45de75
--- /dev/null
+++ b/core/camel-spring-boot/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,19 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+org.springframework.boot.env.EnvironmentPostProcessor=\
+org.apache.camel.spring.boot.CamelVirtualThreadEnvironmentPostProcessor
\ No newline at end of file
diff --git 
a/core/camel-spring-boot/src/test/java21/org/apache/camel/spring/boot/CamelVirtualThreadsTest.java
 
b/core/camel-spring-boot/src/test/java21/org/apache/camel/spring/boot/CamelVirtualThreadsTest.java
new file mode 100644
index 00000000000..e6bc4ec86ab
--- /dev/null
+++ 
b/core/camel-spring-boot/src/test/java21/org/apache/camel/spring/boot/CamelVirtualThreadsTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.spring.boot;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.spring.junit5.CamelSpringBootTest;
+import org.awaitility.Awaitility;
+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.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test to verify that Camel routes use virtual threads when virtual threads 
are enabled.
+ */
+@EnableAutoConfiguration
+@CamelSpringBootTest
+@SpringBootTest(classes = { CamelAutoConfiguration.class, 
CamelVirtualThreadsTest.class, 
+                           CamelVirtualThreadsTest.TestConfiguration.class },
+                properties = { "spring.threads.virtual.enabled=true" })
+public class CamelVirtualThreadsTest {
+
+    // Ensure camel.threads.virtual.enabled is set before any Camel classes 
are loaded
+    static {
+        System.setProperty("camel.threads.virtual.enabled", "true");
+    }
+
+    @Autowired
+    CamelContext context;
+
+    @Configuration
+    public static class TestConfiguration {
+
+        @Bean
+        public RouteBuilder virtualThreadTestRoute() {
+            return new RouteBuilder() {
+                @Override
+                public void configure() {
+                    from("timer:virtualThreadTest?period=100&repeatCount=1")
+                        .routeId("virtualThreadTestRoute")
+                        .process(exchange -> {
+                            // Capture the current thread information
+                            Thread currentThread = Thread.currentThread();
+                            String threadName = currentThread.getName();
+                            boolean isVirtual = currentThread.isVirtual();
+                            
+                            // Store thread information in exchange properties 
for assertion
+                            exchange.setProperty("threadName", threadName);
+                            exchange.setProperty("isVirtual", isVirtual);
+                            exchange.setProperty("threadClass", 
currentThread.getClass().getName());
+                        })
+                        .to("mock:result");
+                }
+            };
+        }
+    }
+
+    @Test
+    public void testCamelVirtualThreadPropertyIsSet() throws Exception {
+        // Verify that the environment post processor set the 
camel.threads.virtual.enabled property
+        String camelVirtualThreadsProperty = 
System.getProperty("camel.threads.virtual.enabled");
+        assertThat(camelVirtualThreadsProperty)
+                .as("camel.threads.virtual.enabled should be automatically set 
by EnvironmentPostProcessor when spring.threads.virtual.enabled=true")
+                .isEqualTo("true");
+    }
+
+    @Test
+    public void testRouteExecutesOnVirtualThread() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Boolean> isVirtualThread = new 
AtomicReference<>(false);
+        AtomicReference<String> threadName = new AtomicReference<>("");
+
+        // Create a test route using SEDA which uses ExecutorService
+        RouteBuilder testRoute = new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("seda:virtualTest?concurrentConsumers=1")
+                    .routeId("virtualTestRoute")
+                    .process(exchange -> {
+                        Thread currentThread = Thread.currentThread();
+                        isVirtualThread.set(currentThread.isVirtual());
+                        threadName.set(currentThread.getName());
+                        latch.countDown();
+                    });
+            }
+        };
+
+        // Add the route dynamically to ensure it uses current context 
configuration
+        try {
+            context.addRoutes(testRoute);
+            
+            // Send a message to trigger the route
+            context.createProducerTemplate().sendBody("seda:virtualTest", 
"test message");
+            
+            // Wait for the route to execute using Awaitility
+            Awaitility.await()
+                    .atMost(2, TimeUnit.SECONDS)
+                    .until(() -> latch.getCount() == 0);
+            
+            // Assert that the route executed on a virtual thread
+            assertThat(isVirtualThread.get())
+                .as("Route should execute on a virtual thread when virtual 
threads are enabled. Thread name: " + threadName.get())
+                .isTrue();
+                
+            assertThat(threadName.get())
+                .as("Virtual thread should have a recognizable name pattern")
+                .isNotEmpty();
+                
+        } finally {
+            // Clean up - remove the test route
+            context.getRouteController().stopRoute("virtualTestRoute");
+            context.removeRoute("virtualTestRoute");
+        }
+    }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 85401b2387e..75b020a5bf5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -130,7 +130,7 @@
         <surefire.version>${maven-surefire-plugin-version}</surefire.version>
         <swagger-parser-v3-version>2.1.10</swagger-parser-v3-version>
         <cyclonedx-maven-plugin-version>2.9.1</cyclonedx-maven-plugin-version>
-        <wiremock-spring-boot-version>3.4.0</wiremock-spring-boot-version>
+        <wiremock-spring-boot-version>3.10.6</wiremock-spring-boot-version>
         
<spring-session-hazelcast-version>3.4.2</spring-session-hazelcast-version>
     </properties>
 
diff --git 
a/tests/camel-itest-spring-boot/src/main/java/org/apache/camel/itest/springboot/ApplicationContextHolder.java
 
b/tests/camel-itest-spring-boot/src/main/java/org/apache/camel/itest/springboot/ApplicationContextHolder.java
index 33f1e40fdf6..66135da078b 100644
--- 
a/tests/camel-itest-spring-boot/src/main/java/org/apache/camel/itest/springboot/ApplicationContextHolder.java
+++ 
b/tests/camel-itest-spring-boot/src/main/java/org/apache/camel/itest/springboot/ApplicationContextHolder.java
@@ -21,22 +21,24 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
 import org.springframework.stereotype.Component;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * receives a spring-context and make it available to classes outside the 
spring scope.
  */
 @Component
 public class ApplicationContextHolder implements ApplicationContextAware {
 
+    private static final CountDownLatch contextLatch = new CountDownLatch(1);
     private static ApplicationContext context;
 
     private static long contextMaxWaitTime = 60000L;
 
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) 
throws BeansException {
-        synchronized (ApplicationContextHolder.class) {
-            ApplicationContextHolder.context = applicationContext;
-            ApplicationContextHolder.class.notifyAll();
-        }
+        ApplicationContextHolder.context = applicationContext;
+        contextLatch.countDown();
     }
 
     public static ApplicationContext getApplicationContext() throws 
InterruptedException {
@@ -46,18 +48,7 @@ public class ApplicationContextHolder implements 
ApplicationContextAware {
 
     private static void waitForContextReady() throws InterruptedException {
         long maxWait = contextMaxWaitTime;
-        long deadline = System.currentTimeMillis() + maxWait;
-        synchronized (ApplicationContextHolder.class) {
-            long time = System.currentTimeMillis();
-            while (time < deadline && context == null) {
-                ApplicationContextHolder.class.wait(deadline - time);
-                time = System.currentTimeMillis();
-            }
-
-            if (context == null) {
-                throw new IllegalStateException("No spring context available 
after " + maxWait + " millis");
-            }
-        }
+        contextLatch.await(maxWait, TimeUnit.MILLISECONDS);
     }
 
     public static long getContextMaxWaitTime() {


Reply via email to