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 5c1c78c83360 CAMEL-24836: PropertyBindingException carries the reason:
the message ends with the root cause's message (#26622)
5c1c78c83360 is described below
commit 5c1c78c8336061e1231eb4f5fcecabdcf55d2744
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Sep 20 12:42:23 2026 +0200
CAMEL-24836: PropertyBindingException carries the reason: the message ends
with the root cause's message (#26622)
The first line said only that the binding failed; the reason (host must be
an absolute URI, no type converter available) sat two causes down the stack
trace. The walk of the cause chain is bounded. Found in the round-2 local-model
benchmark on the camel-jbang-examples ladder.
---
.../org/apache/camel/PropertyBindingException.java | 26 +++++++++-
.../apache/camel/PropertyBindingExceptionTest.java | 56 +++++++++++++++++++++-
2 files changed, 78 insertions(+), 4 deletions(-)
diff --git
a/core/camel-api/src/main/java/org/apache/camel/PropertyBindingException.java
b/core/camel-api/src/main/java/org/apache/camel/PropertyBindingException.java
index 86cc7adcf4e3..99517bfcc51a 100644
---
a/core/camel-api/src/main/java/org/apache/camel/PropertyBindingException.java
+++
b/core/camel-api/src/main/java/org/apache/camel/PropertyBindingException.java
@@ -27,6 +27,8 @@ import org.jspecify.annotations.Nullable;
*/
public class PropertyBindingException extends RuntimeCamelException {
+ private static final int MAX_CAUSE_DEPTH = 100;
+
private final Object target;
private final @Nullable String propertyName;
private final @Nullable Object value;
@@ -99,12 +101,32 @@ public class PropertyBindingException extends
RuntimeCamelException {
if (optionPrefix != null && optionKey != null) {
key = optionPrefix.endsWith(".") ? optionPrefix + optionKey :
optionPrefix + "." + optionKey;
}
+ String reason = rootCauseMessage();
if (key != null) {
return "Error binding property (" + key + "=" + stringValue + ")
with name: " + propertyName
- + " on bean: " + target + " with value: " + stringValue;
+ + " on bean: " + target + " with value: " + stringValue +
(reason != null ? ": " + reason : "");
} else {
- return "Error binding properties on bean: " + target;
+ return "Error binding properties on bean: " + target + (reason !=
null ? ": " + reason : "");
+ }
+ }
+
+ /**
+ * The message of the deepest cause, the reason the binding failed (host
must be an absolute URI, no type converter
+ * available): the first line is what a person reads, and it said only
that the binding failed (CAMEL-24836).
+ */
+ private @Nullable String rootCauseMessage() {
+ Throwable t = getCause();
+ Throwable deepest = null;
+ // bounded: a cause chain assembled outside initCause (a getCause
override, deserialization) may loop
+ for (int i = 0; t != null && i < MAX_CAUSE_DEPTH; i++) {
+ deepest = t;
+ t = t.getCause();
+ }
+ if (deepest == null || deepest instanceof PropertyBindingException) {
+ return null;
}
+ String msg = deepest.getMessage();
+ return msg != null && !msg.isBlank() ? msg.trim() : null;
}
public Object getTarget() {
diff --git
a/core/camel-core/src/test/java/org/apache/camel/PropertyBindingExceptionTest.java
b/core/camel-core/src/test/java/org/apache/camel/PropertyBindingExceptionTest.java
index a3cae1810c93..cde83e0eaaab 100644
---
a/core/camel-core/src/test/java/org/apache/camel/PropertyBindingExceptionTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/PropertyBindingExceptionTest.java
@@ -24,18 +24,70 @@ public class PropertyBindingExceptionTest {
public static final String EXPECTED_EXCEPTION_MESSAGE
= "Error binding property (prefix.property=value) with name:
property";
+ @Test
+ public void messageCarriesTheReason() {
+ // CAMEL-24836: the first line said only that the binding failed; the
reason sat two causes down
+ IllegalArgumentException reason = new IllegalArgumentException(
+ "host must be an absolute URI (e.g. http://api.example.com),
given: `http://localhost:8080/api`");
+ PropertyBindingException inner
+ = new PropertyBindingException(new Object(), "host",
"http://localhost:8080/api", reason);
+ PropertyBindingException outer = new PropertyBindingException(
+ new Object(), "host", "http://localhost:8080/api",
"camel.component.rest-openapi", "host", inner);
+ assertTrue(outer.getMessage().endsWith("with value:
http://localhost:8080/api: host must be an absolute URI"
+ + " (e.g.
http://api.example.com), given: `http://localhost:8080/api`"),
+ outer.getMessage());
+ assertTrue(inner.getMessage().endsWith(": host must be an absolute URI
(e.g. http://api.example.com), given:"
+ + "
`http://localhost:8080/api`"),
+ inner.getMessage());
+ // no reason to add: a cause without a message, or no cause at all
+ PropertyBindingException noReason = new PropertyBindingException(new
Object(), "host", "x", new RuntimeException());
+ assertTrue(noReason.getMessage().endsWith("with value: x"),
noReason.getMessage());
+ PropertyBindingException noCause = new PropertyBindingException(new
Object(), "host", "x");
+ assertTrue(noCause.getMessage().endsWith("with value: x"),
noCause.getMessage());
+ PropertyBindingException noName = new PropertyBindingException(new
Object(), reason);
+ assertTrue(noName.getMessage().endsWith(": host must be an absolute
URI (e.g. http://api.example.com), given:"
+ + "
`http://localhost:8080/api`"),
+ noName.getMessage());
+ }
+
+ @Test
+ public void messageOnACyclicCauseChain() {
+ // a cause chain assembled outside initCause can loop (A -> B -> A);
the walk must still end
+ CyclicException a = new CyclicException("a");
+ CyclicException b = new CyclicException("b");
+ a.next = b;
+ b.next = a;
+ PropertyBindingException pbe = new PropertyBindingException(new
Object(), "host", "x", a);
+ assertTrue(pbe.getMessage().startsWith("Error binding property
(host=x)"), pbe.getMessage());
+ }
+
+ private static final class CyclicException extends RuntimeException {
+ Throwable next;
+
+ CyclicException(String message) {
+ super(message);
+ }
+
+ @Override
+ public synchronized Throwable getCause() {
+ return next;
+ }
+ }
+
@Test
public void exceptionMessageTest() {
PropertyBindingException pbe = new PropertyBindingException(
- new Object(), "property", "value", "prefix", "property", new
Throwable("The casue!"));
+ new Object(), "property", "value", "prefix", "property", new
Throwable("The cause!"));
assertTrue(pbe.getMessage().startsWith(EXPECTED_EXCEPTION_MESSAGE),
"PropertyBindingException message should start with [" +
EXPECTED_EXCEPTION_MESSAGE + "] while is ["
+
pbe.getMessage() + "] instead.");
+ assertTrue(pbe.getMessage().endsWith(": The cause!"),
pbe.getMessage());
pbe = new PropertyBindingException(
- new Object(), "property", "value", "prefix.", "property", new
Throwable("The casue!"));
+ new Object(), "property", "value", "prefix.", "property", new
Throwable("The cause!"));
assertTrue(pbe.getMessage().startsWith(EXPECTED_EXCEPTION_MESSAGE),
"PropertyBindingException message should start with [" +
EXPECTED_EXCEPTION_MESSAGE + "] while is ["
+
pbe.getMessage() + "] instead.");
+ assertTrue(pbe.getMessage().endsWith(": The cause!"),
pbe.getMessage());
}
}