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 + "]";