[
https://issues.apache.org/jira/browse/CAMEL-24320?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen resolved CAMEL-24320.
---------------------------------
Resolution: Fixed
> camel-core - Kamelet route creation fails with virtual threads enabled on JDK
> 25
> --------------------------------------------------------------------------------
>
> Key: CAMEL-24320
> URL: https://issues.apache.org/jira/browse/CAMEL-24320
> Project: Camel
> Issue Type: Bug
> Components: camel-core, camel-kamelet
> Affects Versions: 4.21.0
> Environment: Apache Camel Main 4.21.0; Eclipse Temurin 25.0.3;
> camel.threads.virtual.enabled=true; reproduced in Docker. Control: the
> identical route starts and consumes timer ticks with virtual threads disabled.
> Reporter: Serdar Gökay
> Assignee: Omar Atie
> Priority: Major
> Fix For: 4.22.0
>
> Attachments: camel-kamelet-vt-jdk25-reproducer.zip
>
>
> h3. What happens
> With {{-Dcamel.threads.virtual.enabled=true}} on JDK 25, a route that
> consumes a Kamelet fails during route creation with a
> {{NullPointerException}}. The same route starts and runs normally with
> virtual threads disabled.
> h3. Root cause
> # On JDK 25 with virtual threads enabled, the multi-release
> {{ContextValueFactory}} ({{core/camel-util}}, Java 25 variant) selects the
> {{ScopedValue}}-backed {{ScopedValueContextValue}} because
> {{ThreadType.current()}} reports {{VIRTUAL}}.
> # {{DefaultCamelContextExtension.getCreateRoute()}} and
> {{getCreateProcessor()}} are implemented as {{isCreateRoute.orElse(null)}}
> and {{isCreateProcessor.orElse(null)}}
> ({{DefaultCamelContextExtension.java:330,335}}).
> # {{ScopedValueContextValue.orElse}} ({{ContextValueFactory.java:141}})
> delegates to {{java.lang.ScopedValue.orElse}}, which on JDK 25 executes
> {{Objects.requireNonNull(other)}} before any binding lookup — a null
> fallback therefore throws unconditionally, even where a binding exists.
> # {{KameletComponent}} reads {{getCreateRoute()}} while initializing its
> endpoint service ({{KameletComponent.java:184}}), outside any binding scope,
> so every Kamelet route creation throws NPE before the route exists.
> There is no configuration that keeps virtual threads enabled while forcing
> the {{ThreadLocal}}-backed {{ContextValue}}; the implementation is selected
> solely from {{ThreadType.current()}}. The defect is still present on {{main}}
> as of 2026-07-31 (call sites and implementation unchanged).
> h3. Stack trace (camel 4.21.0, Temurin 25.0.3)
> {code}
> Exception in thread "main" org.apache.camel.FailedToCreateRouteException:
> Failed to create route: vt-kamelet-repro (source: file:route.camel.yaml):
> Route(vt-kamelet-repro)[From[kamelet:vt-repro-source] -> [Lo... because:
> Failed to resolve endpoint: kamelet://vt-repro-source due to: null
> at
> org.apache.camel.reifier.RouteReifier.createRoute(RouteReifier.java:96)
> ...
> Caused by: org.apache.camel.ResolveEndpointFailedException: Failed to resolve
> endpoint: kamelet://vt-repro-source due to: null
> at
> org.apache.camel.impl.engine.AbstractCamelContext.doGetEndpoint(AbstractCamelContext.java:891)
> ...
> Caused by: java.lang.NullPointerException
> at java.base/java.util.Objects.requireNonNull(Unknown Source)
> at java.base/java.lang.ScopedValue.orElse(Unknown Source)
> at
> org.apache.camel.util.concurrent.ContextValueFactory$ScopedValueContextValue.orElse(ContextValueFactory.java:141)
> at
> org.apache.camel.impl.engine.DefaultCamelContextExtension.getCreateRoute(DefaultCamelContextExtension.java:330)
> at
> org.apache.camel.component.kamelet.KameletComponent$1.doInit(KameletComponent.java:184)
> {code}
> h3. Reproducer (plain Camel Main, two small files)
> {{route.camel.yaml}}:
> {code}
> - route:
> id: vt-kamelet-repro
> from:
> uri: kamelet:vt-repro-source
> steps:
> - log: "consumed ${body}"
> {code}
> {{kamelets/vt-repro-source.kamelet.yaml}}:
> {code}
> apiVersion: camel.apache.org/v1
> kind: Kamelet
> metadata:
> name: vt-repro-source
> labels:
> camel.apache.org/kamelet.type: source
> spec:
> definition:
> title: VT repro source
> template:
> from:
> uri: timer:tick
> parameters:
> period: "1000"
> steps:
> - setBody:
> constant: hello
> - to:
> uri: kamelet:sink
> {code}
> Run with any JDK 25 and camel-main 4.21.0 (kamelets/ on the classpath):
> {code}
> java -cp <camel-main-4.21.0-classpath>:. \
> -Dcamel.threads.virtual.enabled=true \
> -Dcamel.main.routesIncludePattern=file:route.camel.yaml \
> org.apache.camel.main.Main
> {code}
> → {{FailedToCreateRouteException}} with the NPE above at startup.
> With {{-Dcamel.threads.virtual.enabled=false}} the identical files start and
> consume timer ticks.
> h3. Suggested fix direction
> Make the {{ScopedValue}}-backed {{orElse}} null-tolerant — e.g. return the
> default when unbound instead of delegating a null fallback
> ({{scopedValue.isBound() ? scopedValue.get() : defaultValue}}) — or change
> the two call sites to a non-null fallback. A regression test that creates a
> Kamelet route with virtual threads enabled on JDK 25 would cover the path;
> note that {{getCreateProcessor()}} carries the same defect for any component
> reading it during endpoint creation.
> Related: CAMEL-20199 (virtual threads umbrella).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)