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 8dc0faa70680 CAMEL-25277: camel-nats - the consumer must not reply 
with the body of a failed exchange (#27306)
8dc0faa70680 is described below

commit 8dc0faa7068022009af55a7f30debcd1c077cd7a
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 3 12:18:27 2026 +0530

    CAMEL-25277: camel-nats - the consumer must not reply with the body of a 
failed exchange (#27306)
    
    An InOut NATS consumer answers a request (a message with a reply subject) 
with the body of the exchange. It did so also when the exchange failed, so the 
requester got a successful reply, usually its own request echoed back, and 
never learned that the request failed. Same family as CAMEL-25250 (grpc) and 
CAMEL-25249 (vertx).
    NATS has no error reply, so this change sends no reply when the exchange 
failed or is marked rollback only, as camel-jms and camel-sjms (without 
`transferException`) and camel-spring-rabbitmq do: the requester times out 
(`ExchangeTimedOutException` after `requestTimeout` with the camel-nats 
producer). The exception is not sent to the requester. A failure that the route 
handles (`onException(...).handled(true)`) is answered as before. The upgrade 
guide for 4.23 gets a note.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/component/nats/NatsConsumer.java  |  5 +-
 .../integration/NatsConsumerReplyToFailureIT.java  | 66 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  9 +++
 3 files changed, 79 insertions(+), 1 deletion(-)

diff --git 
a/components/camel-nats/src/main/java/org/apache/camel/component/nats/NatsConsumer.java
 
b/components/camel-nats/src/main/java/org/apache/camel/component/nats/NatsConsumer.java
index 1630fa1b8e32..af23bed3aac9 100644
--- 
a/components/camel-nats/src/main/java/org/apache/camel/component/nats/NatsConsumer.java
+++ 
b/components/camel-nats/src/main/java/org/apache/camel/component/nats/NatsConsumer.java
@@ -385,9 +385,12 @@ public class NatsConsumer extends DefaultConsumer {
                     NatsConsumer.this.processor.process(exchange);
 
                     // is there a reply? only send reply if exchange pattern 
supports output (InOut)
+                    // NATS has no error reply, so a failed exchange is not 
answered (the requester times out)
+                    // instead of answering with the body of the failed 
exchange as if it succeeded
                     if 
(!NatsConsumingTask.this.configuration.isReplyToDisabled()
                             && msg.getReplyTo() != null && msg.getConnection() 
!= null
-                            && exchange.getPattern().isOutCapable()) {
+                            && exchange.getPattern().isOutCapable()
+                            && !exchange.isFailed() && 
!exchange.isRollbackOnly()) {
                         final Connection con = msg.getConnection();
                         final byte[] data = 
exchange.getMessage().getBody(byte[].class);
                         if (data != null) {
diff --git 
a/components/camel-nats/src/test/java/org/apache/camel/component/nats/integration/NatsConsumerReplyToFailureIT.java
 
b/components/camel-nats/src/test/java/org/apache/camel/component/nats/integration/NatsConsumerReplyToFailureIT.java
new file mode 100644
index 000000000000..eb23a0fe84c7
--- /dev/null
+++ 
b/components/camel-nats/src/test/java/org/apache/camel/component/nats/integration/NatsConsumerReplyToFailureIT.java
@@ -0,0 +1,66 @@
+/*
+ * 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.nats.integration;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.ExchangeTimedOutException;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.test.junit6.TestSupport.assertIsInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * NATS has no error reply: when the exchange of an InOut consumer fails, the 
consumer must not answer with the body of
+ * the failed exchange as if it succeeded, so the requester times out. A 
failure handled by the route is answered.
+ */
+public class NatsConsumerReplyToFailureIT extends NatsITSupport {
+
+    @Test
+    public void testNoReplyWhenExchangeFailed() {
+        CamelExecutionException e = assertThrows(CamelExecutionException.class,
+                () -> template.requestBody("nats:failing?requestTimeout=1000", 
"World", String.class));
+
+        ExchangeTimedOutException cause = 
assertIsInstanceOf(ExchangeTimedOutException.class, e.getCause());
+        assertEquals(1000, cause.getTimeout());
+    }
+
+    @Test
+    public void testReplyWhenFailureHandled() {
+        String reply = 
template.requestBody("nats:handled?requestTimeout=5000", "World", String.class);
+
+        assertEquals("Handled Forced", reply);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("nats:failing?exchangePattern=InOut")
+                        .throwException(new 
IllegalArgumentException("Forced"));
+
+                from("nats:handled?exchangePattern=InOut")
+                        
.onException(IllegalArgumentException.class).handled(true)
+                            .setBody(simple("Handled ${exception.message}"))
+                        .end()
+                        .throwException(new 
IllegalArgumentException("Forced"));
+            }
+        };
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 1a46cac9d9da..f89769709b02 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -1864,6 +1864,15 @@ unparsable content type now fails fast with a 
`ParseException` at marshal time,
 value was written into the `Content-Type` header as-is. Ensure a custom 
`ContentTypeResolver` returns a valid
 MIME type.
 
+=== camel-nats - no reply when the exchange failed
+
+A consumer with `exchangePattern=InOut` answered a request (a message with a 
reply subject) with the body of the
+exchange also when the exchange failed, so the requester got a successful 
reply, usually its own request. NATS has
+no error reply, so the consumer now sends no reply when the exchange failed or 
is marked rollback only, as camel-jms
+does without `transferException`: the requester times out (with the camel-nats 
producer, an
+`ExchangeTimedOutException` after `requestTimeout`). A failure that the route 
handles, for example with
+`onException(...).handled(true)`, is answered as before.
+
 === camel-netty - object codecs apply a deserialization filter by default
 
 The `ObjectDecoder` and `DatagramPacketObjectDecoder` codecs (used when a 
route configures Netty

Reply via email to