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 6899d606d363 CAMEL-25923: camel-core - unmarshal with allowNullBody 
keeps the message when the body is null (#28301)
6899d606d363 is described below

commit 6899d606d363ab6035da9765a2093cf87c59e283
Author: seshuthota <[email protected]>
AuthorDate: Fri Oct 23 13:51:56 2026 +0530

    CAMEL-25923: camel-core - unmarshal with allowNullBody keeps the message 
when the body is null (#28301)
    
    unmarshal with allowNullBody=true dropped the message headers when the body 
was null, because the null-body path used an empty out message. An allowed null 
body now leaves the message as it is, as marshal does, and variableReceive is 
still set to null.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/catalog/docs/unmarshal-eip.adoc   |   3 +-
 .../docs/modules/eips/pages/unmarshal-eip.adoc     |   3 +-
 .../processor/UnmarshalAllowNullBodyTest.java      | 110 +++++++++++++++++++++
 .../camel/processor/UnmarshalProcessorTest.java    |   4 +
 .../support/processor/UnmarshalProcessor.java      |  54 +++++-----
 5 files changed, 146 insertions(+), 28 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc
index c3556e98eaa2..f8d3813eba0c 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/unmarshal-eip.adoc
@@ -130,7 +130,8 @@ YAML::
 
 Sometimes, there are situations where `null` can be a normal value for the 
body of a message but `null` by default is not an accepted value to unmarshal.
 To work around that, it is possible to allow `null` as value to a body to 
unmarshall
-using the option `allowNullBody` as shown in the next code snippets:
+using the option `allowNullBody` as shown in the next code snippets. A `null` 
body then skips the unmarshalling
+and leaves the message as it is, as `marshal` does; with `variableReceive`, 
that variable is set to `null`.
 
 [tabs]
 ====
diff --git 
a/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc 
b/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc
index c3556e98eaa2..f8d3813eba0c 100644
--- a/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc
+++ b/core/camel-core-engine/src/main/docs/modules/eips/pages/unmarshal-eip.adoc
@@ -130,7 +130,8 @@ YAML::
 
 Sometimes, there are situations where `null` can be a normal value for the 
body of a message but `null` by default is not an accepted value to unmarshal.
 To work around that, it is possible to allow `null` as value to a body to 
unmarshall
-using the option `allowNullBody` as shown in the next code snippets:
+using the option `allowNullBody` as shown in the next code snippets. A `null` 
body then skips the unmarshalling
+and leaves the message as it is, as `marshal` does; with `variableReceive`, 
that variable is set to `null`.
 
 [tabs]
 ====
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalAllowNullBodyTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalAllowNullBodyTest.java
new file mode 100644
index 000000000000..fd41e2509def
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalAllowNullBodyTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.processor;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.support.service.ServiceSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class UnmarshalAllowNullBodyTest extends ContextTestSupport {
+
+    @Test
+    public void testAllowNullBodySkipsTheUnmarshalling() {
+        Object out = template.requestBodyAndHeader("direct:allow", null, 
"foo", "bar");
+        assertNull(out);
+    }
+
+    @Test
+    public void testAllowNullBodyKeepsHeaders() throws Exception {
+        getMockEndpoint("mock:allow").expectedHeaderReceived("foo", "bar");
+        template.sendBodyAndHeader("direct:allow", null, "foo", "bar");
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testAllowNullBodyUnmarshalsABody() {
+        Object out = template.requestBody("direct:allow", "Camel");
+        assertEquals("Hello Camel", out);
+    }
+
+    @Test
+    public void testAllowNullBodySetsTheReceiveVariableToNull() throws 
Exception {
+        getMockEndpoint("mock:receive").expectedMessageCount(1);
+        getMockEndpoint("mock:receive").expectedHeaderReceived("foo", "bar");
+        template.sendBodyAndHeader("direct:receive", null, "foo", "bar");
+        assertMockEndpointsSatisfied();
+        // expectedVariableReceived with a null value also accepts any value, 
so check the variable itself
+        
assertNull(getMockEndpoint("mock:receive").getReceivedExchanges().get(0).getVariable("out"));
+    }
+
+    @Test
+    public void testAllowNullVariableLeavesTheMessage() throws Exception {
+        getMockEndpoint("mock:send").expectedBodiesReceived("Camel");
+        getMockEndpoint("mock:send").expectedHeaderReceived("foo", "bar");
+        template.sendBodyAndHeader("direct:send", "Camel", "foo", "bar");
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                context.getRegistry().bind("myDF", new MyHelloDataFormat());
+
+                from("direct:allow")
+                        .unmarshal().allowNullBody().custom("myDF")
+                        .to("mock:allow");
+
+                from("direct:receive")
+                        .setVariable("out", constant("stale"))
+                        
.unmarshal().allowNullBody().variableReceive("out").custom("myDF")
+                        .to("mock:receive");
+
+                from("direct:send")
+                        
.unmarshal().allowNullBody().variableSend("missing").custom("myDF")
+                        .to("mock:send");
+            }
+        };
+    }
+
+    /**
+     * Unmarshals the text to a greeting, and fails on a null body, which 
allowNullBody must skip.
+     */
+    private static class MyHelloDataFormat extends ServiceSupport implements 
DataFormat {
+
+        @Override
+        public void marshal(Exchange exchange, Object graph, OutputStream 
stream) {
+            // not used
+        }
+
+        @Override
+        public Object unmarshal(Exchange exchange, InputStream stream) throws 
Exception {
+            return "Hello " + new String(stream.readAllBytes(), 
StandardCharsets.UTF_8);
+        }
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalProcessorTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalProcessorTest.java
index bc9f71589641..57c4c9d906c1 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalProcessorTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/UnmarshalProcessorTest.java
@@ -90,12 +90,16 @@ public class UnmarshalProcessorTest extends TestSupport {
     @Test
     public void testAllowNullBody() throws Exception {
         Exchange exchange = createExchangeWithBody(new DefaultCamelContext(), 
null);
+        exchange.getIn().setHeader("foo", "bar");
+        Message in = exchange.getIn();
         Processor processor = new UnmarshalProcessor(new 
MyDataFormat(exchange), true);
 
         processor.process(exchange);
 
         assertNull(exchange.getMessage().getBody(), "UnmarshalProcessor should 
allow null body");
         assertNull(exchange.getException(), "UnmarshalProcessor should allow 
null body");
+        assertSame(in, exchange.getMessage(), "UnmarshalProcessor should leave 
the message as it is");
+        assertEquals("bar", exchange.getMessage().getHeader("foo"), 
"UnmarshalProcessor should keep the headers");
     }
 
     @Test
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
index e4cc72d3e158..056c8fb13bf0 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
@@ -80,13 +80,15 @@ public class UnmarshalProcessor extends 
AsyncProcessorSupport
             if (variableSend != null) {
                 body = ExchangeHelper.getVariable(exchange, variableSend);
             }
-            final Message out;
             if (allowNullBody && body == null) {
-                // The body is null, and it is an allowed value so let's skip 
the unmarshalling
-                out = exchange.getOut();
+                // the body is null, and it is an allowed value so let's skip 
the unmarshalling
+                // and leave the message as it is
+                if (variableReceive != null) {
+                    ExchangeHelper.setVariable(exchange, variableReceive, 
null);
+                }
             } else {
                 // lets set up the out message before we invoke the dataFormat 
so that it can mutate it if necessary
-                out = exchange.getOut();
+                final Message out = exchange.getOut();
                 out.copyFrom(in);
                 if (body instanceof InputStream is) {
                     stream = is;
@@ -94,29 +96,29 @@ public class UnmarshalProcessor extends 
AsyncProcessorSupport
                 } else {
                     result = dataFormat.unmarshal(exchange, body);
                 }
-            }
-            if (result instanceof Exchange) {
-                if (result != exchange) {
-                    // it's not allowed to return another exchange other than 
the one provided to dataFormat
-                    throw new RuntimeCamelException(
-                            "The returned exchange " + result + " is not the 
same as " + exchange
-                                                    + " provided to the 
DataFormat");
-                }
-            } else if (result instanceof Message msg) {
-                // result should be stored in variable instead of message body
-                if (variableReceive != null) {
-                    Object value = msg.getBody();
-                    ExchangeHelper.setVariable(exchange, variableReceive, 
value);
-                } else {
-                    // the dataformat has probably set headers, attachments, 
etc. so let's use it as the outbound payload
-                    exchange.setOut(msg);
-                }
-            } else {
-                // result should be stored in variable instead of message body
-                if (variableReceive != null) {
-                    ExchangeHelper.setVariable(exchange, variableReceive, 
result);
+                if (result instanceof Exchange) {
+                    if (result != exchange) {
+                        // it's not allowed to return another exchange other 
than the one provided to dataFormat
+                        throw new RuntimeCamelException(
+                                "The returned exchange " + result + " is not 
the same as " + exchange
+                                                        + " provided to the 
DataFormat");
+                    }
+                } else if (result instanceof Message msg) {
+                    // result should be stored in variable instead of message 
body
+                    if (variableReceive != null) {
+                        Object value = msg.getBody();
+                        ExchangeHelper.setVariable(exchange, variableReceive, 
value);
+                    } else {
+                        // the dataformat has probably set headers, 
attachments, etc. so let's use it as the outbound payload
+                        exchange.setOut(msg);
+                    }
                 } else {
-                    out.setBody(result);
+                    // result should be stored in variable instead of message 
body
+                    if (variableReceive != null) {
+                        ExchangeHelper.setVariable(exchange, variableReceive, 
result);
+                    } else {
+                        out.setBody(result);
+                    }
                 }
             }
         } catch (Exception e) {

Reply via email to