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