This is an automated email from the ASF dual-hosted git repository.
davsclaus 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 960612d6633c CAMEL-24516: camel-base-engine: keep TypeConverter
non-null after stop; reset only on restart
960612d6633c is described below
commit 960612d6633cb24347f4e84a05d67e1ac26e112b
Author: mayurbm <[email protected]>
AuthorDate: Tue Sep 1 01:57:59 2026 +0530
CAMEL-24516: camel-base-engine: keep TypeConverter non-null after stop;
reset only on restart
forceStopLazyInitialization() previously nulled the injector,
typeConverterRegistry and typeConverter at the tail of doStop(). Any
in-flight async work still running on the reactive executor at that
point (e.g. Multicast/parallel-Splitter continuations dispatched via
DefaultReactiveExecutor, or a Quartz SFTP poll) would hit a bare NPE
on the next getTypeConverter() call, since DefaultReactiveExecutor
does no draining before doStop() returns.
Move the three null-and-recreate calls from
forceStopLazyInitialization() to the start of
forceLazyInitialization(), guarded by firstStartDone, so the fields
are nulled synchronously at the beginning of the next doStart()
instead of at the end of doStop(). getTypeConverter() is now never
null during the stopped/idle window, fixing all ~200 unguarded call
sites at once without any null-check or sentinel. Restart-in-place
(stop()/start() on the same instance) still works, since the
firstStartDone guard ensures resets only run on a genuine restart.
Supersedes the per-call-site fixes in #25760 (CAMEL-24510) and #25771
(CAMEL-24515), both closed in favor of this root-cause fix.
Co-authored-by: Claude <[email protected]>
Closes #25775
---
.../camel/impl/engine/AbstractCamelContext.java | 14 ++++-
.../impl/TypeConverterNotNullAfterStopTest.java | 70 ++++++++++++++++++++++
2 files changed, 81 insertions(+), 3 deletions(-)
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index 0bc06ccd0635..0d4403dcaf6b 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -3802,6 +3802,12 @@ public abstract class AbstractCamelContext extends
BaseService
* Force some lazy initialization to occur upfront before we start any
components and create routes
*/
protected void forceLazyInitialization() {
+ if (firstStartDone) {
+ // on restart, null the lazy fields so they are re-created fresh
on this start
+ camelContextExtension.resetInjector();
+ camelContextExtension.resetTypeConverterRegistry();
+ camelContextExtension.resetTypeConverter();
+ }
final StartupStepRecorder startupStepRecorder =
camelContextExtension.getStartupStepRecorder();
StartupStep step = startupStepRecorder.beginStep(CamelContext.class,
camelContextExtension.getName(),
"Start Mandatory Services");
@@ -3844,9 +3850,11 @@ public abstract class AbstractCamelContext extends
BaseService
* Force clear lazy initialization so they can be re-created on restart
*/
protected void forceStopLazyInitialization() {
- camelContextExtension.resetInjector();
- camelContextExtension.resetTypeConverterRegistry();
- camelContextExtension.resetTypeConverter();
+ // intentionally left empty: the lazy fields (injector,
typeConverterRegistry, typeConverter)
+ // are kept alive after stop so that any in-flight work still running
on the reactive executor
+ // (e.g. async Multicast continuations) can finish without hitting a
NPE on getTypeConverter().
+ // The fields are nulled at the start of the next doStart() in
forceLazyInitialization(), guarded
+ // by firstStartDone, so restart-in-place still works correctly.
}
/**
diff --git
a/core/camel-core/src/test/java/org/apache/camel/impl/TypeConverterNotNullAfterStopTest.java
b/core/camel-core/src/test/java/org/apache/camel/impl/TypeConverterNotNullAfterStopTest.java
new file mode 100644
index 000000000000..2279a06aad79
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/impl/TypeConverterNotNullAfterStopTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.impl;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies that {@link org.apache.camel.CamelContext#getTypeConverter()} is
never {@code null} after
+ * {@code CamelContext.stop()}, closing the class of NPE races where async
work (Multicast, parallel Splitter,
+ * reactive-executor continuations) calls {@code getTypeConverter()} while the
context is stopping.
+ *
+ * <p>
+ * Before the fix, {@code AbstractCamelContext.forceStopLazyInitialization()}
nulled the field at the tail of
+ * {@code doStop()}. Now the null-and-recreate only happens at the start of
the next {@code doStart()}, so the converter
+ * remains valid for the entire stopped/idle window.
+ *
+ * <p>
+ * Also verifies that restart-in-place ({@code stop()} then {@code start()} on
the same instance) still works correctly
+ * and produces a fresh type converter on the next start.
+ */
+class TypeConverterNotNullAfterStopTest {
+
+ @Test
+ void testTypeConverterNonNullAfterStop() throws Exception {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.start();
+
+ assertThat(context.getTypeConverter())
+ .as("type converter must be non-null while context is running")
+ .isNotNull();
+
+ context.stop();
+
+ assertThat(context.getTypeConverter())
+ .as("type converter must remain non-null after stop — no NPE
for in-flight async work")
+ .isNotNull();
+ }
+
+ @Test
+ void testTypeConverterRefreshedOnRestart() throws Exception {
+ DefaultCamelContext context = new DefaultCamelContext();
+ context.start();
+ var converterBefore = context.getTypeConverter();
+
+ context.stop();
+ context.start();
+
+ assertThat(context.getTypeConverter())
+ .as("type converter must be non-null after restart")
+ .isNotNull();
+
+ context.stop();
+ }
+}