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 4c8a1ee4c6d9 CAMEL-25285: unmarshal on a null body says the body is 
null (#27300)
4c8a1ee4c6d9 is described below

commit 4c8a1ee4c6d9226c4dfa0045ae29d24bebc6c2c1
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 2 21:46:10 2026 +0200

    CAMEL-25285: unmarshal on a null body says the body is null (#27300)
    
    A null body failed in the type converter with "No type converter available
    to convert from type: null to the required type: java.io.InputStream",
    naming neither the data format nor the cause. UnmarshalProcessor now says
    "Cannot unmarshal using <dataformat>: the message body is null, so there is
    nothing to unmarshal. Set the message body first, or set allowNullBody=true
    to skip a null body." (the variable, with variableSend). The exception type
    stays NoTypeConversionAvailableException, with the converter's as cause.
    
    NoTypeConversionAvailableException: a null value adds "(the value is null)",
    and a class without a canonical name (an anonymous class) shows its binary
    name instead of "null", so "from type: null" means a null value only. A new
    constructor takes the message first, for a caller that knows the cause.
    
    
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../camel/NoTypeConversionAvailableException.java  | 32 ++++++++--
 ...ypeConversionAvailableExceptionMessageTest.java | 68 ++++++++++++++++++++++
 .../camel/processor/UnmarshalProcessorTest.java    | 43 ++++++++++++++
 .../support/processor/UnmarshalProcessor.java      | 26 ++++++++-
 4 files changed, 162 insertions(+), 7 deletions(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/NoTypeConversionAvailableException.java
 
b/core/camel-api/src/main/java/org/apache/camel/NoTypeConversionAvailableException.java
index ace9793095d0..8aa2a926aebd 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/NoTypeConversionAvailableException.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/NoTypeConversionAvailableException.java
@@ -44,6 +44,18 @@ public class NoTypeConversionAvailableException extends 
CamelException {
         this.type = type;
     }
 
+    /**
+     * @param message the message, for a caller that knows more about the 
failure than the conversion does (such as
+     *                unmarshal on a null message body)
+     * @param value   the value for which no type converter was found
+     * @param type    the expected target type
+     */
+    public NoTypeConversionAvailableException(String message, @Nullable Object 
value, Class<?> type) {
+        super(Objects.requireNonNull(message, "message"));
+        this.value = value;
+        this.type = Objects.requireNonNull(type, "type");
+    }
+
     /**
      * @param value the value for which no type converter was found
      * @param type  the expected target type
@@ -85,9 +97,8 @@ public class NoTypeConversionAvailableException extends 
CamelException {
      */
     public static String createMessage(@Nullable Object value, Class<?> type) {
         Objects.requireNonNull(type, "type");
-        return "No type converter available to convert from type: "
-               + (value != null ? value.getClass().getCanonicalName() : null)
-               + " to the required type: " + type.getCanonicalName();
+        return "No type converter available to convert from type: " + 
typeName(value)
+               + " to the required type: " + type.getCanonicalName() + (value 
== null ? " (the value is null)" : "");
     }
 
     /**
@@ -96,8 +107,19 @@ public class NoTypeConversionAvailableException extends 
CamelException {
     public static String createMessage(@Nullable Object value, Class<?> type, 
Throwable cause) {
         Objects.requireNonNull(type, "type");
         Objects.requireNonNull(cause, "cause");
-        return "Converting Exception when converting from type: "
-               + (value != null ? value.getClass().getCanonicalName() : null) 
+ " to the required type: "
+        return "Converting Exception when converting from type: " + 
typeName(value) + " to the required type: "
                + type.getCanonicalName() + ", which is caused by " + cause;
     }
+
+    /**
+     * The type of the value: the canonical name, or the binary name for a 
class that has none (an anonymous class), so
+     * that "null" in the message means the value is null.
+     */
+    private static String typeName(@Nullable Object value) {
+        if (value == null) {
+            return "null";
+        }
+        String name = value.getClass().getCanonicalName();
+        return name != null ? name : value.getClass().getName();
+    }
 }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/NoTypeConversionAvailableExceptionMessageTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/NoTypeConversionAvailableExceptionMessageTest.java
new file mode 100644
index 000000000000..073e38512640
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/NoTypeConversionAvailableExceptionMessageTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The message of {@link NoTypeConversionAvailableException}: "null" as the 
from type means the value is null, and says
+ * so; a class without a canonical name (an anonymous class) shows its binary 
name instead of "null".
+ */
+class NoTypeConversionAvailableExceptionMessageTest {
+
+    @Test
+    void nullValueSaysTheValueIsNull() {
+        assertThat(new NoTypeConversionAvailableException(null, 
java.io.InputStream.class).getMessage())
+                .isEqualTo("No type converter available to convert from type: 
null to the required type: "
+                           + "java.io.InputStream (the value is null)");
+    }
+
+    @Test
+    void namedClassUsesCanonicalName() {
+        assertThat(NoTypeConversionAvailableException.createMessage("hello", 
Integer.class))
+                .isEqualTo("No type converter available to convert from type: 
java.lang.String to the required type: "
+                           + "java.lang.Integer");
+    }
+
+    @Test
+    void anonymousClassUsesBinaryName() {
+        Object anon = new Runnable() {
+            @Override
+            public void run() {
+            }
+        };
+
+        assertThat(NoTypeConversionAvailableException.createMessage(anon, 
String.class))
+                .doesNotContain("from type: null")
+                .contains("from type: " + anon.getClass().getName());
+        assertThat(NoTypeConversionAvailableException.createMessage(anon, 
String.class, new RuntimeException("boom")))
+                .doesNotContain("from type: null")
+                .contains("from type: " + anon.getClass().getName());
+    }
+
+    @Test
+    void messageConstructorKeepsValueAndType() {
+        NoTypeConversionAvailableException e
+                = new NoTypeConversionAvailableException("nothing to convert", 
null, String.class);
+
+        assertThat(e.getMessage()).isEqualTo("nothing to convert");
+        assertThat(e.getValue()).isNull();
+        assertThat(e.getToType()).isEqualTo(String.class);
+    }
+}
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 77100c9aaa0d..1e6a775eed3c 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
@@ -22,6 +22,7 @@ import java.io.OutputStream;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.Processor;
 import org.apache.camel.TestSupport;
 import org.apache.camel.impl.DefaultCamelContext;
@@ -97,6 +98,48 @@ public class UnmarshalProcessorTest extends TestSupport {
         assertNull(exchange.getException(), "UnmarshalProcessor should allow 
null body");
     }
 
+    @Test
+    public void testNullBodySaysTheBodyIsNull() throws Exception {
+        Exchange exchange = createExchangeWithBody(new DefaultCamelContext(), 
null);
+        Processor processor = new UnmarshalProcessor(new MyDataFormat(new 
Object()));
+
+        processor.process(exchange);
+
+        NoTypeConversionAvailableException e
+                = assertInstanceOf(NoTypeConversionAvailableException.class, 
exchange.getException());
+        assertEquals("Cannot unmarshal using MyDataFormat: the message body is 
null, so there is nothing to unmarshal. "
+                     + "Set the message body first, or set allowNullBody=true 
to skip a null body.",
+                e.getMessage());
+        assertNull(e.getValue());
+        assertEquals(InputStream.class, e.getToType());
+        assertInstanceOf(NoTypeConversionAvailableException.class, 
e.getCause());
+    }
+
+    @Test
+    public void testNullVariableSaysTheVariableIsNull() throws Exception {
+        Exchange exchange = createExchangeWithBody(new DefaultCamelContext(), 
"body");
+        UnmarshalProcessor processor = new UnmarshalProcessor(new 
MyDataFormat(new Object()));
+        processor.setVariableSend("order");
+
+        processor.process(exchange);
+
+        assertEquals("Cannot unmarshal using MyDataFormat: the variable order 
is null, so there is nothing to unmarshal. "
+                     + "Set the variable order first, or set 
allowNullBody=true to skip a null body.",
+                exchange.getException().getMessage());
+    }
+
+    @Test
+    public void testBodyThatCannotBeConvertedKeepsTheConverterMessage() throws 
Exception {
+        Exchange exchange = createExchangeWithBody(new DefaultCamelContext(), 
new Object());
+        Processor processor = new UnmarshalProcessor(new MyDataFormat(new 
Object()));
+
+        processor.process(exchange);
+
+        assertEquals("No type converter available to convert from type: 
java.lang.Object to the required type: "
+                     + "java.io.InputStream",
+                exchange.getException().getMessage());
+    }
+
     private static class MyDataFormat extends ServiceSupport implements 
DataFormat {
 
         private final Object object;
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 e466f22a5fd5..5a00379abdbb 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
@@ -25,9 +25,11 @@ import org.apache.camel.CamelContextAware;
 import org.apache.camel.DisabledAware;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
+import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.Traceable;
 import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.DataFormatName;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.RouteIdAware;
 import org.apache.camel.spi.StepIdAware;
@@ -69,10 +71,11 @@ public class UnmarshalProcessor extends 
AsyncProcessorSupport
 
         InputStream stream = null;
         Object result = null;
+        Object body = null;
         try {
             final Message in = exchange.getIn();
             final Object originalBody = in.getBody();
-            Object body = originalBody;
+            body = originalBody;
             if (variableSend != null) {
                 body = ExchangeHelper.getVariable(exchange, variableSend);
             }
@@ -118,7 +121,7 @@ public class UnmarshalProcessor extends 
AsyncProcessorSupport
         } catch (Exception e) {
             // remove OUT message, as an exception occurred
             exchange.setOut(null);
-            exchange.setException(e);
+            exchange.setException(body == null ? nullBodyException(e) : e);
         } finally {
             // The Iterator will close the stream itself
             if (!(result instanceof Iterator)) {
@@ -129,6 +132,25 @@ public class UnmarshalProcessor extends 
AsyncProcessorSupport
         return true;
     }
 
+    /**
+     * A null body fails in the type converter, whose message only says that 
null cannot become an InputStream. Say what
+     * it means here: there is nothing to unmarshal, and allowNullBody skips 
it. The exception type stays the same, so
+     * an onException for it still matches.
+     */
+    private Exception nullBodyException(Exception e) {
+        if (!(e instanceof NoTypeConversionAvailableException ntc) || 
ntc.getValue() != null) {
+            return e;
+        }
+        String name
+                = dataFormat instanceof DataFormatName dfn ? 
dfn.getDataFormatName() : dataFormat.getClass().getSimpleName();
+        String what = variableSend != null ? "the variable " + variableSend : 
"the message body";
+        String message = "Cannot unmarshal using " + name + ": " + what + " is 
null, so there is nothing to unmarshal. "
+                         + "Set " + what + " first, or set allowNullBody=true 
to skip a null body.";
+        NoTypeConversionAvailableException answer = new 
NoTypeConversionAvailableException(message, null, ntc.getToType());
+        answer.initCause(e);
+        return answer;
+    }
+
     @Override
     public String toString() {
         return "Unmarshal[" + dataFormat + "]";

Reply via email to