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 046f719fb7c4 CAMEL-26536: camel-jms - a consumer stopped and started 
again with asyncStopListener=true must keep a running listener container 
(#29120)
046f719fb7c4 is described below

commit 046f719fb7c41c88e3cf851199adfb51a0591ea0
Author: allthingssecurity <[email protected]>
AuthorDate: Wed Nov 4 15:06:10 2026 +0530

    CAMEL-26536: camel-jms - a consumer stopped and started again with 
asyncStopListener=true must keep a running listener container (#29120)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/component/jms/JmsConsumer.java    | 166 ++++++++---
 .../jms/JmsAsyncStopListenerRestartTest.java       | 321 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   8 +
 3 files changed, 455 insertions(+), 40 deletions(-)

diff --git 
a/components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsConsumer.java
 
b/components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsConsumer.java
index 7be15c1e46d2..2047ab8d2056 100644
--- 
a/components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsConsumer.java
+++ 
b/components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsConsumer.java
@@ -17,6 +17,8 @@
 package org.apache.camel.component.jms;
 
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import jakarta.jms.Connection;
 import jakarta.jms.ConnectionFactory;
@@ -49,6 +51,8 @@ public class JmsConsumer extends DefaultConsumer implements 
Suspendable {
     private volatile boolean initialized;
     private volatile ExecutorService executorService;
     private volatile boolean shutdownExecutorService;
+    // makes the ownership check of the asynchronous start task atomic with 
the detach in doStop
+    private final Lock containerLock = new ReentrantLock();
 
     public JmsConsumer(JmsEndpoint endpoint, Processor processor, 
AbstractMessageListenerContainer listenerContainer) {
         super(endpoint, processor);
@@ -111,9 +115,13 @@ public class JmsConsumer extends DefaultConsumer 
implements Suspendable {
      * Can be used to start this consumer later if it was configured to not 
auto startup.
      */
     public void startListenerContainer() {
-        LOG.trace("Starting listener container {} on destination {}", 
listenerContainer, getDestinationName());
-        listenerContainer.start();
-        LOG.debug("Started listener container {} on destination {}", 
listenerContainer, getDestinationName());
+        startListenerContainer(listenerContainer);
+    }
+
+    private void startListenerContainer(AbstractMessageListenerContainer 
container) {
+        LOG.trace("Starting listener container {} on destination {}", 
container, getDestinationName(container));
+        container.start();
+        LOG.debug("Started listener container {} on destination {}", 
container, getDestinationName(container));
     }
 
     /**
@@ -124,9 +132,13 @@ public class JmsConsumer extends DefaultConsumer 
implements Suspendable {
      * @throws FailedToCreateConsumerException is thrown if testing the 
connection failed
      */
     protected void testConnectionOnStartup() throws 
FailedToCreateConsumerException {
+        testConnectionOnStartup(listenerContainer);
+    }
+
+    private void testConnectionOnStartup(AbstractMessageListenerContainer 
container) throws FailedToCreateConsumerException {
         try {
-            LOG.debug("Testing JMS Connection on startup for destination: {}", 
getDestinationName());
-            ConnectionFactory connectionfactory = 
listenerContainer.getConnectionFactory();
+            LOG.debug("Testing JMS Connection on startup for destination: {}", 
getDestinationName(container));
+            ConnectionFactory connectionfactory = 
container.getConnectionFactory();
             if (connectionfactory != null) {
                 Connection con = connectionfactory.createConnection();
                 JmsUtils.closeConnection(con);
@@ -134,9 +146,9 @@ public class JmsConsumer extends DefaultConsumer implements 
Suspendable {
                 LOG.error("connection factory is null");
                 throw new IllegalStateException("connection factory is null");
             }
-            LOG.debug("Successfully tested JMS Connection on startup for 
destination: {}", getDestinationName());
+            LOG.debug("Successfully tested JMS Connection on startup for 
destination: {}", getDestinationName(container));
         } catch (Exception e) {
-            String msg = "Cannot get JMS Connection on startup for destination 
" + getDestinationName();
+            String msg = "Cannot get JMS Connection on startup for destination 
" + getDestinationName(container);
             throw new FailedToCreateConsumerException(getEndpoint(), msg, e);
         }
     }
@@ -149,6 +161,7 @@ public class JmsConsumer extends DefaultConsumer implements 
Suspendable {
         if (listenerContainer == null) {
             createMessageListenerContainer();
         }
+        final AbstractMessageListenerContainer container = listenerContainer;
         getEndpoint().onListenerContainerStarting();
 
         if (getEndpoint().getConfiguration().isAsyncStartListener()) {
@@ -156,84 +169,144 @@ public class JmsConsumer extends DefaultConsumer 
implements Suspendable {
                 @Override
                 public void run() {
                     try {
-                        prepareAndStartListenerContainer();
+                        prepareAndStartListenerContainer(container);
                     } catch (Exception e) {
                         LOG.warn("Error starting listener container on 
destination: {}. This exception will be ignored.",
-                                getDestinationName(), e);
+                                getDestinationName(container), e);
                     }
                 }
 
                 @Override
                 public String toString() {
-                    return "AsyncStartListenerTask[" + getDestinationName() + 
"]";
+                    return "AsyncStartListenerTask[" + 
getDestinationName(container) + "]";
                 }
             });
         } else {
-            prepareAndStartListenerContainer();
+            prepareAndStartListenerContainer(container);
         }
     }
 
     protected void prepareAndStartListenerContainer() {
-        listenerContainer.afterPropertiesSet();
+        prepareAndStartListenerContainer(listenerContainer);
+    }
+
+    private void 
prepareAndStartListenerContainer(AbstractMessageListenerContainer container) {
+        if (listenerContainer != container) {
+            // an early exit without the lock: the check that decides is the 
one under containerLock below.
+            // The consumer was stopped (and maybe started again with a new 
listener container) before this
+            // asynchronous start task ran
+            LOG.debug("Not starting listener container {} on destination {} as 
the consumer no longer uses it", container,
+                    getDestinationName(container));
+            return;
+        }
+
+        container.afterPropertiesSet();
 
         // only start listener if auto start is enabled or we are explicit 
invoking start later
         if (initialized || getEndpoint().isAutoStartup()) {
             // should we pre test connections before starting?
             if (getEndpoint().isTestConnectionOnStartup()) {
-                testConnectionOnStartup();
+                testConnectionOnStartup(container);
             }
-            startListenerContainer();
+            startListenerContainer(container);
         }
 
-        // mark as initialized after the auto-startup check
-        initialized = true;
+        containerLock.lock();
+        try {
+            if (listenerContainer == container) {
+                // mark as initialized after the auto-startup check
+                initialized = true;
+                return;
+            }
+        } finally {
+            containerLock.unlock();
+        }
+        // the consumer was stopped while this task was starting the 
container, and its stop may have run before
+        // container.start(): stop and destroy it here
+        LOG.debug("Stopping listener container {} on destination {} as the 
consumer was stopped while starting it",
+                container, getDestinationName(container));
+        try {
+            container.stop();
+        } finally {
+            container.destroy();
+        }
     }
 
+    /**
+     * Stops and destroys the listener container of this consumer, which 
creates a new one when it is started again.
+     */
     protected void stopAndDestroyListenerContainer() {
-        if (listenerContainer != null) {
+        DetachedListenerContainer detached = detachListenerContainer();
+        if (detached != null) {
+            stopAndDestroyListenerContainer(detached);
+        }
+    }
+
+    /**
+     * Takes the listener container (and its private thread pool) away from 
this consumer, so a later start creates a
+     * new one and the (possibly asynchronous) stop of this one cannot touch 
it.
+     */
+    private DetachedListenerContainer detachListenerContainer() {
+        containerLock.lock();
+        try {
+            AbstractMessageListenerContainer container = listenerContainer;
+            if (container == null) {
+                return null;
+            }
+            DetachedListenerContainer answer
+                    = new DetachedListenerContainer(container, 
executorService, shutdownExecutorService);
+            // null container and listener so they are fully re created if 
this consumer is restarted
+            // then we will use updated configuration from jms endpoint that 
may have been managed using JMX
+            listenerContainer = null;
+            messageListener = null;
+            initialized = false;
+            executorService = null;
+            return answer;
+        } finally {
+            containerLock.unlock();
+        }
+    }
+
+    private void stopAndDestroyListenerContainer(DetachedListenerContainer 
detached) {
+        try {
             try {
-                listenerContainer.stop();
-                listenerContainer.destroy();
+                detached.container().stop();
             } finally {
-                getEndpoint().onListenerContainerStopped();
+                // also when stop fails (a SimpleMessageListenerContainer does 
not swallow a failing Connection.stop())
+                detached.container().destroy();
+            }
+        } finally {
+            getEndpoint().onListenerContainerStopped();
+            // shutdown thread pool if listener container was using a private 
thread pool
+            if (detached.shutdownExecutorService() && 
detached.executorService() != null) {
+                
getEndpoint().getCamelContext().getExecutorServiceManager().shutdownNow(detached.executorService());
             }
         }
-        // null container and listener so they are fully re created if this 
consumer is restarted
-        // then we will use updated configuration from jms endpoint that may 
have been managed using JMX
-        listenerContainer = null;
-        messageListener = null;
-        initialized = false;
-
-        // shutdown thread pool if listener container was using a private 
thread pool
-        if (shutdownExecutorService && executorService != null) {
-            
getEndpoint().getCamelContext().getExecutorServiceManager().shutdownNow(executorService);
-        }
-        executorService = null;
     }
 
     @Override
     protected void doStop() throws Exception {
-        if (listenerContainer != null) {
-
+        final DetachedListenerContainer detached = detachListenerContainer();
+        if (detached != null) {
             if (getEndpoint().getConfiguration().isAsyncStopListener()) {
                 getEndpoint().getAsyncStartStopExecutorService().submit(new 
Runnable() {
                     @Override
                     public void run() {
                         try {
-                            stopAndDestroyListenerContainer();
+                            stopAndDestroyListenerContainer(detached);
                         } catch (Exception e) {
                             LOG.warn("Error stopping listener container on 
destination: {}. This exception will be ignored.",
-                                    getDestinationName(), e);
+                                    getDestinationName(detached.container()), 
e);
                         }
                     }
 
                     @Override
                     public String toString() {
-                        return "AsyncStopListenerTask[" + getDestinationName() 
+ "]";
+                        return "AsyncStopListenerTask[" + 
getDestinationName(detached.container()) + "]";
                     }
                 });
             } else {
-                stopAndDestroyListenerContainer();
+                stopAndDestroyListenerContainer(detached);
             }
         }
 
@@ -263,10 +336,16 @@ public class JmsConsumer extends DefaultConsumer 
implements Suspendable {
     }
 
     private String getDestinationName() {
-        if (listenerContainer.getDestination() != null) {
-            return listenerContainer.getDestination().toString();
+        return getDestinationName(listenerContainer);
+    }
+
+    private static String getDestinationName(AbstractMessageListenerContainer 
container) {
+        if (container == null) {
+            return null;
+        } else if (container.getDestination() != null) {
+            return container.getDestination().toString();
         } else {
-            return listenerContainer.getDestinationName();
+            return container.getDestinationName();
         }
     }
 
@@ -295,4 +374,11 @@ public class JmsConsumer extends DefaultConsumer 
implements Suspendable {
         }
     }
 
+    /**
+     * A listener container detached from this consumer by a stop, with the 
private thread pool it uses (if any).
+     */
+    private record DetachedListenerContainer(AbstractMessageListenerContainer 
container, ExecutorService executorService,
+            boolean shutdownExecutorService) {
+    }
+
 }
diff --git 
a/components/camel-jms/src/test/java/org/apache/camel/component/jms/JmsAsyncStopListenerRestartTest.java
 
b/components/camel-jms/src/test/java/org/apache/camel/component/jms/JmsAsyncStopListenerRestartTest.java
new file mode 100644
index 000000000000..59df575e3295
--- /dev/null
+++ 
b/components/camel-jms/src/test/java/org/apache/camel/component/jms/JmsAsyncStopListenerRestartTest.java
@@ -0,0 +1,321 @@
+/*
+ * 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.jms;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import jakarta.jms.ConnectionFactory;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.infra.core.CamelContextExtension;
+import org.apache.camel.test.infra.core.DefaultCamelContextExtension;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.springframework.jms.listener.AbstractMessageListenerContainer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Stopping and starting a JMS consumer (as route policies and JMX do) while 
the asynchronous stop or start of its
+ * listener container is still pending must leave the consumer consuming.
+ * <p/>
+ * The tasks the consumer submits to the component's async start/stop thread 
pool are held, and the test runs them
+ * itself in a chosen order, so the interleaving is deterministic.
+ */
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+@Timeout(60)
+public class JmsAsyncStopListenerRestartTest extends AbstractJMSTest {
+
+    @Order(2)
+    @RegisterExtension
+    public static CamelContextExtension camelContextExtension = new 
DefaultCamelContextExtension();
+
+    private static final String QUEUE = "JmsAsyncStopListenerRestartTest";
+
+    // static: the CamelContext (and so the component) is created once for the 
class
+    private static final HeldExecutorService HELD = new HeldExecutorService();
+    private volatile Runnable initializeHook;
+    private volatile AbstractMessageListenerContainer initializing;
+    protected CamelContext context;
+    protected ProducerTemplate template;
+
+    @Test
+    public void testRestartWhileAsyncStopIsPending() throws Exception {
+        context.getRouteController().startRoute("jms");
+        assertConsumes("Hello World");
+
+        consumer().stop();
+        consumer().start();
+        assertEquals(1, HELD.pending(), "the asynchronous stop of the old 
listener container is pending");
+
+        // the stop task of the first run of the route runs after the route 
was started again
+        HELD.runPending();
+
+        assertListening();
+        assertConsumes("Bye World");
+    }
+
+    @Test
+    public void testRestartWhileAsyncStopAndStartArePendingStopFirst() throws 
Exception {
+        startRouteWithAsyncStart();
+
+        consumer().stop();
+        consumer().start();
+        assertEquals(2, HELD.pending(), "the stop task and the start task are 
pending");
+
+        HELD.runPending();
+
+        assertListening();
+        assertConsumes("Bye World");
+    }
+
+    @Test
+    public void testRestartWhileAsyncStopAndStartArePendingStartFirst() throws 
Exception {
+        startRouteWithAsyncStart();
+
+        consumer().stop();
+        consumer().start();
+        assertEquals(2, HELD.pending(), "the stop task and the start task are 
pending");
+
+        HELD.runPendingInReverse();
+
+        assertListening();
+        assertConsumes("Bye World");
+    }
+
+    @Test
+    public void testStopWhileAsyncStartIsPending() throws Exception {
+        int running = endpoint().getRunningMessageListeners();
+        startRouteWithAsyncStart();
+
+        consumer().stop();
+        consumer().start();
+        AbstractMessageListenerContainer second = 
consumer().getListenerContainer();
+        consumer().stop();
+
+        // the start task of the second run of the route runs after the route 
was stopped
+        HELD.runPendingInReverse();
+
+        assertTrue(consumer().isStopped(), "the consumer was stopped");
+        assertFalse(second.isActive() && second.isRunning(), "the stopped 
route must not keep a listener container running");
+        assertEquals(running, endpoint().getRunningMessageListeners(),
+                "every started listener container is counted as stopped");
+    }
+
+    @Test
+    public void testStopWhileAsyncStartIsInitializingTheListenerContainer() 
throws Exception {
+        // the listener containers run the hook (if any) when they are 
initialized
+        endpoint().getConfiguration().setMessageListenerContainerFactory(e -> 
new DefaultJmsMessageListenerContainer(e) {
+            @Override
+            public void afterPropertiesSet() {
+                Runnable hook = initializeHook;
+                initializeHook = null;
+                if (hook != null) {
+                    initializing = this;
+                    hook.run();
+                }
+                super.afterPropertiesSet();
+            }
+        });
+        endpoint().getConfiguration().setConsumerType(ConsumerType.Custom);
+        startRouteWithAsyncStart();
+
+        consumer().stop();
+        consumer().start();
+        // the consumer is stopped, and its stop task runs, after the start 
task checked that the consumer owns the
+        // listener container and before the start task initializes and starts 
it
+        initializeHook = () -> {
+            consumer().stop();
+            HELD.runPending();
+        };
+        HELD.runPending();
+
+        assertTrue(consumer().isStopped(), "the consumer was stopped");
+        assertNotNull(initializing, "the start task initialized the listener 
container");
+        assertFalse(initializing.isActive() && initializing.isRunning(),
+                "the stopped consumer must not keep a listener container 
running");
+    }
+
+    private void startRouteWithAsyncStart() throws Exception {
+        endpoint().getConfiguration().setAsyncStartListener(true);
+        context.getRouteController().startRoute("jms");
+        HELD.runPending();
+        assertConsumes("Hello World");
+    }
+
+    private JmsEndpoint endpoint() {
+        return context.getEndpoint("activemq:queue:" + QUEUE, 
JmsEndpoint.class);
+    }
+
+    private JmsConsumer consumer() {
+        return (JmsConsumer) context.getRoute("jms").getConsumer();
+    }
+
+    private void assertListening() throws Exception {
+        assertTrue(consumer().isStarted(), "the consumer was started");
+        assertTrue(consumer().getListenerContainer().isRunning(),
+                "the started consumer must have a running listener container");
+    }
+
+    private void assertConsumes(String body) throws Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.reset();
+        result.expectedBodiesReceived(body);
+        template.sendBody("activemq:queue:" + QUEUE, body);
+        result.assertIsSatisfied();
+    }
+
+    @Override
+    protected String getComponentName() {
+        return "activemq";
+    }
+
+    @Override
+    protected JmsComponent buildComponent(ConnectionFactory connectionFactory) 
{
+        JmsComponent jms = new JmsComponent() {
+            @Override
+            protected ExecutorService getAsyncStartStopExecutorService() {
+                return HELD;
+            }
+        };
+        jms.getConfiguration().setConnectionFactory(connectionFactory);
+        jms.getConfiguration().setAsyncStopListener(true);
+        return jms;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from("activemq:queue:" + 
QUEUE).routeId("jms").autoStartup(false).to("mock:result");
+            }
+        };
+    }
+
+    @Override
+    public CamelContextExtension getCamelContextExtension() {
+        return camelContextExtension;
+    }
+
+    @BeforeEach
+    void setUpRequirements() {
+        context = camelContextExtension.getContext();
+        template = camelContextExtension.getProducerTemplate();
+        HELD.hold = true;
+    }
+
+    @AfterEach
+    void stopRoute() throws Exception {
+        // run what is left, and stop the route with the tasks running inline, 
for the next test
+        HELD.hold = false;
+        HELD.runPending();
+        context.getRouteController().stopRoute("jms");
+        endpoint().getConfiguration().setAsyncStartListener(false);
+        endpoint().getConfiguration().setMessageListenerContainerFactory(null);
+        endpoint().getConfiguration().setConsumerType(ConsumerType.Default);
+        // drop what a failed test left in the queue, so it does not reach the 
next test
+        while 
(camelContextExtension.getConsumerTemplate().receiveBody("activemq:queue:" + 
QUEUE, 100) != null) {
+            // drained
+        }
+    }
+
+    /**
+     * An executor that keeps the submitted tasks until the test runs them (in 
submission order or in reverse order).
+     */
+    static final class HeldExecutorService extends AbstractExecutorService {
+        private final List<Runnable> tasks = new ArrayList<>();
+        volatile boolean hold = true;
+
+        @Override
+        public void execute(Runnable command) {
+            if (hold) {
+                synchronized (tasks) {
+                    tasks.add(command);
+                }
+            } else {
+                command.run();
+            }
+        }
+
+        int pending() {
+            synchronized (tasks) {
+                return tasks.size();
+            }
+        }
+
+        void runPending() {
+            runPending(false);
+        }
+
+        void runPendingInReverse() {
+            runPending(true);
+        }
+
+        private void runPending(boolean reverse) {
+            List<Runnable> copy;
+            synchronized (tasks) {
+                copy = new ArrayList<>(tasks);
+                tasks.clear();
+            }
+            if (reverse) {
+                Collections.reverse(copy);
+            }
+            copy.forEach(Runnable::run);
+        }
+
+        @Override
+        public void shutdown() {
+            // noop
+        }
+
+        @Override
+        public List<Runnable> shutdownNow() {
+            return List.of();
+        }
+
+        @Override
+        public boolean isShutdown() {
+            return false;
+        }
+
+        @Override
+        public boolean isTerminated() {
+            return false;
+        }
+
+        @Override
+        public boolean awaitTermination(long timeout, TimeUnit unit) {
+            return true;
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index ed00683ba05a..f23049e8871e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -3418,6 +3418,14 @@ comment after the transition was made, `watchers` and 
`addIssue` can fail after
 (`addIssue` sets the created issue as the message body before it adds the 
watchers), and a retry or redelivery
 repeats the earlier requests.
 
+=== camel-jms - JmsConsumer starts and stops its listener container through 
private methods
+
+`JmsConsumer.doStart` and `doStop` no longer call the protected methods 
`prepareAndStartListenerContainer()`,
+`stopAndDestroyListenerContainer()` and `testConnectionOnStartup()`, so that a 
consumer stopped and started again
+(with `asyncStopListener=true` or `asyncStartListener=true`) always works on 
its own listener container. The methods
+are still there. A subclass of `JmsConsumer` (created by a custom 
`JmsEndpoint.createConsumer`) that overrides them
+to change how the listener container is started or stopped should override 
`doStart` and `doStop` instead.
+
 === camel-jms - preserveMessageQos uses the time to live of the endpoint for a 
message without expiration
 
 With `preserveMessageQos=true`, a message without a `JMSExpiration` (header 
missing or 0) was sent with no time to

Reply via email to