This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 4ec79f7f8a38 CAMEL-20199: Allow enabling virtual threads via
configuration property (#21743)
4ec79f7f8a38 is described below
commit 4ec79f7f8a38cb26c98caed6ab6a3539f42ca595
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri Mar 6 20:57:36 2026 +0100
CAMEL-20199: Allow enabling virtual threads via configuration property
(#21743)
Add camel.main.virtualThreadsEnabled property so virtual threads can be
enabled from application.properties without requiring a JVM system property.
The property is read early during bootstrap and sets the system property
before any thread pools are created. ThreadType is changed to lazy
initialization to support this.
---
.../main/camel-main-configuration-metadata.json | 1 +
.../MainConfigurationPropertiesConfigurer.java | 7 +++++
.../camel-main-configuration-metadata.json | 1 +
core/camel-main/src/main/docs/main.adoc | 3 +-
.../org/apache/camel/main/BaseMainSupport.java | 9 ++++++
.../camel/main/MainConfigurationProperties.java | 26 ++++++++++++++++
.../camel/util/concurrent/CamelThreadFactory.java | 4 +--
.../apache/camel/util/concurrent/ThreadType.java | 36 ++++++++++++++--------
.../camel/util/concurrent/ContextValueFactory.java | 28 ++++++++++-------
.../modules/ROOT/pages/virtual-threads.adoc | 21 ++++++++-----
10 files changed, 102 insertions(+), 34 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index e0e8c6b15390..4907b8539795 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -155,6 +155,7 @@
{ "name": "camel.main.useDataType", "required": false, "description":
"Whether to enable using data type on Camel messages. Data type are automatic
turned on if one ore more routes has been explicit configured with input and
output types. Otherwise data type is default off.", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.main.useMdcLogging", "required": false, "description":
"To turn on MDC logging", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.main.uuidGenerator", "required": false, "description":
"UUID generator to use. default (32 bytes), short (16 bytes), classic (32 bytes
or longer), simple (long incrementing counter), off (turned off for exchanges -
only intended for performance profiling)", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "enum",
"javaType": "java.lang.String", "defaultValue": "default", "secret": false,
"enum": [ "classic", "default", "short", "simple", [...]
+ { "name": "camel.main.virtualThreadsEnabled", "required": false,
"description": "Whether to enable virtual threads when creating thread pools.
When enabled, Camel will use virtual threads instead of platform threads for
its thread pools. This can also be enabled via the JVM system property {code
camel.threads.virtual.enabled=true} . This option must be read early during
bootstrap, so it is set as a system property before thread pools are created.",
"sourceType": "org.apache.camel.mai [...]
{ "name": "camel.debug.bodyIncludeFiles", "required": false,
"description": "Whether to include the message body of file based messages. The
overhead is that the file content has to be read from the file.", "sourceType":
"org.apache.camel.main.DebuggerConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": true, "secret": false },
{ "name": "camel.debug.bodyIncludeStreams", "required": false,
"description": "Whether to include the message body of stream based messages.
If enabled then beware the stream may not be re-readable later. See more about
Stream Caching.", "sourceType":
"org.apache.camel.main.DebuggerConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.debug.bodyMaxChars", "required": false, "description":
"To limit the message body to a maximum size in the traced message. Use 0 or
negative value to use unlimited size.", "sourceType":
"org.apache.camel.main.DebuggerConfigurationProperties", "type": "integer",
"javaType": "int", "defaultValue": 32768, "secret": false },
diff --git
a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
index 5ee376adff5d..5a0ac2359a10 100644
---
a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
+++
b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
@@ -148,6 +148,7 @@ public class MainConfigurationPropertiesConfigurer extends
org.apache.camel.supp
map.put("UseDataType", boolean.class);
map.put("UseMdcLogging", boolean.class);
map.put("UuidGenerator", java.lang.String.class);
+ map.put("VirtualThreadsEnabled", boolean.class);
ALL_OPTIONS = map;
}
@@ -401,6 +402,8 @@ public class MainConfigurationPropertiesConfigurer extends
org.apache.camel.supp
case "useMdcLogging": target.setUseMdcLogging(property(camelContext,
boolean.class, value)); return true;
case "uuidgenerator":
case "uuidGenerator": target.setUuidGenerator(property(camelContext,
java.lang.String.class, value)); return true;
+ case "virtualthreadsenabled":
+ case "virtualThreadsEnabled":
target.setVirtualThreadsEnabled(property(camelContext, boolean.class, value));
return true;
default: return false;
}
}
@@ -659,6 +662,8 @@ public class MainConfigurationPropertiesConfigurer extends
org.apache.camel.supp
case "useMdcLogging": return boolean.class;
case "uuidgenerator":
case "uuidGenerator": return java.lang.String.class;
+ case "virtualthreadsenabled":
+ case "virtualThreadsEnabled": return boolean.class;
default: return null;
}
}
@@ -913,6 +918,8 @@ public class MainConfigurationPropertiesConfigurer extends
org.apache.camel.supp
case "useMdcLogging": return target.isUseMdcLogging();
case "uuidgenerator":
case "uuidGenerator": return target.getUuidGenerator();
+ case "virtualthreadsenabled":
+ case "virtualThreadsEnabled": return target.isVirtualThreadsEnabled();
default: return null;
}
}
diff --git
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index e0e8c6b15390..4907b8539795 100644
---
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -155,6 +155,7 @@
{ "name": "camel.main.useDataType", "required": false, "description":
"Whether to enable using data type on Camel messages. Data type are automatic
turned on if one ore more routes has been explicit configured with input and
output types. Otherwise data type is default off.", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.main.useMdcLogging", "required": false, "description":
"To turn on MDC logging", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.main.uuidGenerator", "required": false, "description":
"UUID generator to use. default (32 bytes), short (16 bytes), classic (32 bytes
or longer), simple (long incrementing counter), off (turned off for exchanges -
only intended for performance profiling)", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "enum",
"javaType": "java.lang.String", "defaultValue": "default", "secret": false,
"enum": [ "classic", "default", "short", "simple", [...]
+ { "name": "camel.main.virtualThreadsEnabled", "required": false,
"description": "Whether to enable virtual threads when creating thread pools.
When enabled, Camel will use virtual threads instead of platform threads for
its thread pools. This can also be enabled via the JVM system property {code
camel.threads.virtual.enabled=true} . This option must be read early during
bootstrap, so it is set as a system property before thread pools are created.",
"sourceType": "org.apache.camel.mai [...]
{ "name": "camel.debug.bodyIncludeFiles", "required": false,
"description": "Whether to include the message body of file based messages. The
overhead is that the file content has to be read from the file.", "sourceType":
"org.apache.camel.main.DebuggerConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": true, "secret": false },
{ "name": "camel.debug.bodyIncludeStreams", "required": false,
"description": "Whether to include the message body of stream based messages.
If enabled then beware the stream may not be re-readable later. See more about
Stream Caching.", "sourceType":
"org.apache.camel.main.DebuggerConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.debug.bodyMaxChars", "required": false, "description":
"To limit the message body to a maximum size in the traced message. Use 0 or
negative value to use unlimited size.", "sourceType":
"org.apache.camel.main.DebuggerConfigurationProperties", "type": "integer",
"javaType": "int", "defaultValue": 32768, "secret": false },
diff --git a/core/camel-main/src/main/docs/main.adoc
b/core/camel-main/src/main/docs/main.adoc
index 179bc82bb8e3..2d71b3062514 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -19,7 +19,7 @@ The following tables lists all the options:
// main options: START
=== Camel Main configurations
-The camel.main supports 126 options, which are listed below.
+The camel.main supports 127 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -150,6 +150,7 @@ The camel.main supports 126 options, which are listed below.
| *camel.main.useDataType* | Whether to enable using data type on Camel
messages. Data type are automatic turned on if one ore more routes has been
explicit configured with input and output types. Otherwise data type is default
off. | false | boolean
| *camel.main.useMdcLogging* | To turn on MDC logging | false | boolean
| *camel.main.uuidGenerator* | UUID generator to use. default (32 bytes),
short (16 bytes), classic (32 bytes or longer), simple (long incrementing
counter), off (turned off for exchanges - only intended for performance
profiling) | default | String
+| *camel.main.virtualThreads{zwsp}Enabled* | Whether to enable virtual threads
when creating thread pools. When enabled, Camel will use virtual threads
instead of platform threads for its thread pools. This can also be enabled via
the JVM system property {code camel.threads.virtual.enabled=true} . This option
must be read early during bootstrap, so it is set as a system property before
thread pools are created. | false | boolean
|===
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index 9224f4c2cd40..451174cda706 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -577,6 +577,15 @@ public abstract class BaseMainSupport extends BaseService {
autoConfigurationPropertiesComponent(camelContext,
autoConfiguredProperties);
recorder.endStep(step);
step = recorder.beginStep(BaseMainSupport.class,
"autoConfigurationSingleOption", "Auto Configure");
+ autoConfigurationSingleOption(camelContext,
autoConfiguredProperties, "camel.main.virtualThreadsEnabled",
+ value -> {
+ boolean enabled = Boolean.parseBoolean(value);
+
mainConfigurationProperties.setVirtualThreadsEnabled(enabled);
+ if (enabled) {
+
System.setProperty("camel.threads.virtual.enabled", "true");
+ }
+ return null;
+ });
autoConfigurationSingleOption(camelContext,
autoConfiguredProperties, "camel.main.routesIncludePattern",
value -> {
mainConfigurationProperties.setRoutesIncludePattern(value);
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
index c95aab733879..795a854f72ae 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
@@ -46,6 +46,7 @@ public class MainConfigurationProperties extends
DefaultConfigurationProperties<
private int extraShutdownTimeout = 15;
private String basePackageScan;
private boolean basePackageScanEnabled = true;
+ private boolean virtualThreadsEnabled;
private String mainListenerClasses;
private String routesBuilderClasses;
@@ -586,6 +587,23 @@ public class MainConfigurationProperties extends
DefaultConfigurationProperties<
this.basePackageScanEnabled = basePackageScanEnabled;
}
+ public boolean isVirtualThreadsEnabled() {
+ return virtualThreadsEnabled;
+ }
+
+ /**
+ * Whether to enable virtual threads when creating thread pools.
+ *
+ * When enabled, Camel will use virtual threads instead of platform
threads for its thread pools. This can also be
+ * enabled via the JVM system property {@code
camel.threads.virtual.enabled=true}.
+ *
+ * This option must be read early during bootstrap, so it is set as a
system property before thread pools are
+ * created.
+ */
+ public void setVirtualThreadsEnabled(boolean virtualThreadsEnabled) {
+ this.virtualThreadsEnabled = virtualThreadsEnabled;
+ }
+
public int getDurationHitExitCode() {
return durationHitExitCode;
}
@@ -912,6 +930,14 @@ public class MainConfigurationProperties extends
DefaultConfigurationProperties<
return this;
}
+ /**
+ * Whether to enable virtual threads when creating thread pools.
+ */
+ public MainConfigurationProperties withVirtualThreadsEnabled(boolean
virtualThreadsEnabled) {
+ this.virtualThreadsEnabled = virtualThreadsEnabled;
+ return this;
+ }
+
// fluent builders - main listener
// --------------------------------------------------------------
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/CamelThreadFactory.java
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/CamelThreadFactory.java
index 9c179c7ba198..4bb630479974 100644
---
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/CamelThreadFactory.java
+++
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/CamelThreadFactory.java
@@ -26,8 +26,6 @@ import org.slf4j.LoggerFactory;
public final class CamelThreadFactory implements ThreadFactoryTypeAware {
private static final Logger LOG =
LoggerFactory.getLogger(CamelThreadFactory.class);
- private static final ThreadFactoryType TYPE = ThreadFactoryType.current();
-
private final String pattern;
private final String name;
private final boolean daemon;
@@ -37,7 +35,7 @@ public final class CamelThreadFactory implements
ThreadFactoryTypeAware {
this.pattern = pattern;
this.name = name;
this.daemon = daemon;
- this.threadType = daemon ? TYPE : ThreadFactoryType.PLATFORM;
+ this.threadType = daemon ? ThreadFactoryType.current() :
ThreadFactoryType.PLATFORM;
}
@Override
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadType.java
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadType.java
index c43926be2df6..074048e3d9a5 100644
---
a/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadType.java
+++
b/core/camel-util/src/main/java/org/apache/camel/util/concurrent/ThreadType.java
@@ -20,25 +20,37 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * Defines the existing type of threads. The virtual threads can only be used
with the system property
- * {@code camel.threads.virtual.enabled} set to {@code true}. The default
value is {@code false} which means that
- * platform threads are used by default.
+ * Defines the existing type of threads. The virtual threads can be enabled
with the system property
+ * {@code camel.threads.virtual.enabled} set to {@code true}, or via the Camel
Main configuration property
+ * {@code camel.main.virtualThreadsEnabled}. The default value is {@code
false} which means that platform threads are
+ * used by default.
+ * <p>
+ * The thread type is resolved lazily on first access, allowing configuration
properties to set the system property
+ * before the type is determined.
*/
public enum ThreadType {
PLATFORM,
VIRTUAL;
private static final Logger LOG =
LoggerFactory.getLogger(ThreadType.class);
- private static final ThreadType CURRENT =
Boolean.getBoolean("camel.threads.virtual.enabled") ? VIRTUAL : PLATFORM;
- static {
- if (CURRENT == VIRTUAL) {
- LOG.info("The type of thread detected is: {}", CURRENT);
- } else {
- LOG.debug("The type of thread detected is: {}", CURRENT);
- }
- }
+ private static volatile ThreadType current;
public static ThreadType current() {
- return CURRENT;
+ ThreadType type = current;
+ if (type == null) {
+ synchronized (ThreadType.class) {
+ type = current;
+ if (type == null) {
+ type = Boolean.getBoolean("camel.threads.virtual.enabled")
? VIRTUAL : PLATFORM;
+ current = type;
+ if (type == VIRTUAL) {
+ LOG.info("The type of thread detected is: {}", type);
+ } else {
+ LOG.debug("The type of thread detected is: {}", type);
+ }
+ }
+ }
+ }
+ return type;
}
}
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 b8b835d295df..bd089e18ebcd 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
@@ -30,20 +30,26 @@ import org.slf4j.LoggerFactory;
class ContextValueFactory {
private static final Logger LOG =
LoggerFactory.getLogger(ContextValueFactory.class);
- private static final boolean USE_SCOPED_VALUES = shouldUseScopedValues();
- static {
- if (USE_SCOPED_VALUES) {
- LOG.info("ContextValue will use ScopedValue for virtual thread
optimization");
- } else {
- LOG.debug("ContextValue will use ThreadLocal");
+ // Use lazy holder pattern to avoid resolving ThreadType before
configuration is loaded
+ private static final class ScopedValueHolder {
+ static final boolean USE_SCOPED_VALUES = shouldUseScopedValues();
+
+ static {
+ if (useScopedValues()) {
+ LOG.info("ContextValue will use ScopedValue for virtual thread
optimization");
+ } else {
+ LOG.debug("ContextValue will use ThreadLocal");
+ }
+ }
+
+ private static boolean shouldUseScopedValues() {
+ return ThreadType.current() == ThreadType.VIRTUAL;
}
}
- private static boolean shouldUseScopedValues() {
- // Only use ScopedValue when virtual threads are enabled
- // ScopedValue is immutable and designed for the "pass context through
call chain" pattern
- return ThreadType.current() == ThreadType.VIRTUAL;
+ private static boolean useScopedValues() {
+ return ScopedValueHolder.USE_SCOPED_VALUES;
}
/**
@@ -52,7 +58,7 @@ class ContextValueFactory {
* Uses ScopedValue when virtual threads are enabled, otherwise
ThreadLocal.
*/
static <T> ContextValue<T> newInstance(String name) {
- if (USE_SCOPED_VALUES) {
+ if (useScopedValues()) {
return new ScopedValueContextValue<>(name);
}
return new ThreadLocalContextValue<>(name);
diff --git a/docs/user-manual/modules/ROOT/pages/virtual-threads.adoc
b/docs/user-manual/modules/ROOT/pages/virtual-threads.adoc
index a86a2d0e96a9..06d8501a018e 100644
--- a/docs/user-manual/modules/ROOT/pages/virtual-threads.adoc
+++ b/docs/user-manual/modules/ROOT/pages/virtual-threads.adoc
@@ -54,23 +54,30 @@ Virtual threads are *opt-in* in Apache Camel. When enabled,
Camel's thread pool
=== Global Configuration
-==== System Property
+==== Camel Main Configuration Property
-[source,bash]
+The recommended way to enable virtual threads is via the Camel Main
configuration property.
+This works in camel-main, camel-jbang, and camel-spring-boot:
+
+[source,properties]
----
-java -Dcamel.threads.virtual.enabled=true -jar myapp.jar
+camel.main.virtualThreadsEnabled=true
----
-==== Application Properties (Spring Boot / Quarkus)
+This property is read early during bootstrap, before thread pools are created,
so it takes effect globally.
-[source,properties]
+==== System Property
+
+Alternatively, you can use a JVM system property:
+
+[source,bash]
----
-camel.threads.virtual.enabled=true
+java -Dcamel.threads.virtual.enabled=true -jar myapp.jar
----
==== Programmatic Configuration
-For custom setups, the thread type is determined at JVM startup based on the
system property. Camel's `ThreadType.current()` returns either `PLATFORM` or
`VIRTUAL`.
+For custom setups, the thread type is determined lazily on first access based
on the system property. Camel's `ThreadType.current()` returns either
`PLATFORM` or `VIRTUAL`.
=== What Changes When Enabled