This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-24513
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 92ee348526c13029c1aa6bfc6b50df36a4b5b158
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Aug 27 07:06:24 2026 +0200

    CAMEL-24513: camel-core - Error registry for handled exception should point 
to origin of error
    
    DefaultErrorRegistry captured the failing node id/location by reading the 
last
    message history entry, which is only correct if nothing runs after the
    failure. For a handled exception, the exception handler itself (onException,
    doCatch, dead letter channel) can append its own message history entries
    before the registry looks, so toNode/location ended up pointing at a node
    touched by the handler instead of the node that actually failed.
    
    Add Exchange.FAILURE_NODE_ID / Exchange.FAILURE_LOCATION, captured up-front
    via ExchangeHelper.captureFailureOrigin() as soon as the exception is caught
    (RedeliveryErrorHandler, TryProcessor), before any failure processor runs.
    DefaultErrorRegistry now reads this snapshot for both handled and unhandled
    errors, falling back to the previous last-entry heuristic only when no error
    handler captured it (e.g. noErrorHandler()).
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/catalog/docs/dead-letter-channel.adoc    | 20 ++++++++++
 .../org/apache/camel/catalog/models/doCatch.json   |  4 +-
 .../org/apache/camel/catalog/models/doFinally.json |  4 +-
 .../apache/camel/catalog/models/errorHandler.json  |  4 +-
 .../apache/camel/catalog/models/onException.json   |  4 +-
 .../org/apache/camel/spring/xml/errorHandler.json  |  4 +-
 .../org/apache/camel/ExchangeConstantProvider.java |  4 +-
 .../src/main/java/org/apache/camel/Exchange.java   |  8 ++++
 .../java/org/apache/camel/ExchangePropertyKey.java |  6 +++
 .../camel/impl/engine/DefaultErrorRegistry.java    | 29 ++++++++------
 .../modules/eips/pages/dead-letter-channel.adoc    | 20 ++++++++++
 .../META-INF/org/apache/camel/model/doCatch.json   |  4 +-
 .../META-INF/org/apache/camel/model/doFinally.json |  4 +-
 .../org/apache/camel/model/errorHandler.json       |  4 +-
 .../org/apache/camel/model/onException.json        |  4 +-
 .../org/apache/camel/processor/CatchProcessor.java |  5 ---
 .../apache/camel/processor/FinallyProcessor.java   |  7 +---
 .../org/apache/camel/processor/TryProcessor.java   |  9 +++++
 .../ShareUnitOfWorkAggregationStrategy.java        |  8 ++++
 .../errorhandler/RedeliveryErrorHandler.java       | 16 +++-----
 .../org/apache/camel/impl/ErrorRegistryTest.java   | 44 ++++++++++++++++++++++
 .../org/apache/camel/support/ExchangeHelper.java   | 29 ++++++++++++++
 22 files changed, 199 insertions(+), 42 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc
index 132bceab0b0e..37d1a28e2fbc 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/dead-letter-channel.adoc
@@ -633,6 +633,26 @@ The `Exchange.FAILURE_ROUTE_ID` have the constant value 
`CamelFailureRouteId`.
 This allows, for example, you to fetch this information in your dead letter 
queue and use that for error reporting.
 
 
+=== Which node failed?
+
+Camel also stores the id of the node (EIP processor) where the message failed, 
along with its source
+code location (requires source location to be enabled). This information can 
be obtained in Java via:
+
+._Java-only: accessing the failure node id and location from the Exchange_
+[source,java]
+----
+String failedNodeId = exchange.getProperty(Exchange.FAILURE_NODE_ID, 
String.class);
+String failedLocation = exchange.getProperty(Exchange.FAILURE_LOCATION, 
String.class);
+----
+
+The `Exchange.FAILURE_NODE_ID` have the constant value `CamelFailureNodeId`, 
and `Exchange.FAILURE_LOCATION`
+have the constant value `CamelFailureLocation`.
+
+This information is captured up-front, as soon as the exception is caught, so 
it always points to the node
+that actually failed - even when the error handler (`onException`, `doCatch`, 
or the dead letter channel
+itself) performs further processing of its own before the exchange is moved to 
the dead letter queue.
+
+
 === Control if redelivery is allowed during stopping/shutdown
 
 The option `allowRedeliveryWhileStopping` (default is `true`) controls whether 
redelivery is allowed or not,
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json
index fe1bcc91fd59..b1a537a05ede 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doCatch.json
@@ -23,6 +23,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json
index d8b5516eb41e..0ece8a5dcdc7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/doFinally.json
@@ -21,6 +21,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/errorHandler.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/errorHandler.json
index bea634af9bb9..12e4b0912735 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/errorHandler.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/errorHandler.json
@@ -18,6 +18,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json
index 1063517cde30..355a81d775dc 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/onException.json
@@ -32,6 +32,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/components/camel-spring-parent/camel-spring-xml/src/generated/resources/META-INF/org/apache/camel/spring/xml/errorHandler.json
 
b/components/camel-spring-parent/camel-spring-xml/src/generated/resources/META-INF/org/apache/camel/spring/xml/errorHandler.json
index 5de738fc2e52..090a8ea0ccc1 100644
--- 
a/components/camel-spring-parent/camel-spring-xml/src/generated/resources/META-INF/org/apache/camel/spring/xml/errorHandler.json
+++ 
b/components/camel-spring-parent/camel-spring-xml/src/generated/resources/META-INF/org/apache/camel/spring/xml/errorHandler.json
@@ -34,6 +34,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/core/camel-api/src/generated/java/org/apache/camel/ExchangeConstantProvider.java
 
b/core/camel-api/src/generated/java/org/apache/camel/ExchangeConstantProvider.java
index 9b93e7bb3081..a9d27b5a2870 100644
--- 
a/core/camel-api/src/generated/java/org/apache/camel/ExchangeConstantProvider.java
+++ 
b/core/camel-api/src/generated/java/org/apache/camel/ExchangeConstantProvider.java
@@ -17,7 +17,7 @@ public class ExchangeConstantProvider {
 
     private static final Map<String, String> MAP;
     static {
-        Map<String, String> map = new HashMap<>(156);
+        Map<String, String> map = new HashMap<>(158);
         map.put("ACTIVITY_SPAN_TAGS", "CamelActivitySpanTags");
         map.put("AGGREGATED_COLLECTION_GUARD", 
"CamelAggregatedCollectionGuard");
         map.put("AGGREGATED_COMPLETED_BY", "CamelAggregatedCompletedBy");
@@ -61,6 +61,8 @@ public class ExchangeConstantProvider {
         map.put("EXCEPTION_HANDLED", "CamelExceptionHandled");
         map.put("FAILURE_ENDPOINT", "CamelFailureEndpoint");
         map.put("FAILURE_HANDLED", "CamelFailureHandled");
+        map.put("FAILURE_LOCATION", "CamelFailureLocation");
+        map.put("FAILURE_NODE_ID", "CamelFailureNodeId");
         map.put("FAILURE_ROUTE_ID", "CamelFailureRouteId");
         map.put("FATAL_FALLBACK_ERROR_HANDLER", 
"CamelFatalFallbackErrorHandler");
         map.put("FILE_CONTENT_TYPE", "CamelFileContentType");
diff --git a/core/camel-api/src/main/java/org/apache/camel/Exchange.java 
b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
index 74457a08e0e8..b5ca93d302d1 100644
--- a/core/camel-api/src/main/java/org/apache/camel/Exchange.java
+++ b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
@@ -142,6 +142,14 @@ public interface Exchange extends VariableAware {
               description = "Route ID where the Exchange failed during 
processing",
               javaType = "String")
     String FAILURE_ROUTE_ID = "CamelFailureRouteId";
+    @Metadata(label = "doCatch,doFinally,errorHandler,onException",
+              description = "Node ID where the Exchange failed during 
processing",
+              javaType = "String")
+    String FAILURE_NODE_ID = "CamelFailureNodeId";
+    @Metadata(label = "doCatch,doFinally,errorHandler,onException",
+              description = "Source code location where the Exchange failed 
during processing",
+              javaType = "String")
+    String FAILURE_LOCATION = "CamelFailureLocation";
     String FATAL_FALLBACK_ERROR_HANDLER = "CamelFatalFallbackErrorHandler";
     String FILE_CONTENT_TYPE = "CamelFileContentType";
     String FILE_LOCAL_WORK_PATH = "CamelFileLocalWorkPath";
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java 
b/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java
index c3d9ed220f05..8a90ce54e5d7 100644
--- a/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java
+++ b/core/camel-api/src/main/java/org/apache/camel/ExchangePropertyKey.java
@@ -62,6 +62,8 @@ public enum ExchangePropertyKey {
     FAILURE_ENDPOINT(Exchange.FAILURE_ENDPOINT),
     FAILURE_HANDLED(Exchange.FAILURE_HANDLED),
     FAILURE_ROUTE_ID(Exchange.FAILURE_ROUTE_ID),
+    FAILURE_NODE_ID(Exchange.FAILURE_NODE_ID),
+    FAILURE_LOCATION(Exchange.FAILURE_LOCATION),
     FATAL_FALLBACK_ERROR_HANDLER(Exchange.FATAL_FALLBACK_ERROR_HANDLER),
     FILE_EXCHANGE_FILE(Exchange.FILE_EXCHANGE_FILE),
     GROUPED_EXCHANGE(Exchange.GROUPED_EXCHANGE),
@@ -175,6 +177,10 @@ public enum ExchangePropertyKey {
                 return FAILURE_ENDPOINT;
             case Exchange.FAILURE_ROUTE_ID:
                 return FAILURE_ROUTE_ID;
+            case Exchange.FAILURE_NODE_ID:
+                return FAILURE_NODE_ID;
+            case Exchange.FAILURE_LOCATION:
+                return FAILURE_LOCATION;
             case Exchange.FATAL_FALLBACK_ERROR_HANDLER:
                 return FATAL_FALLBACK_ERROR_HANDLER;
             case Exchange.FILE_EXCHANGE_FILE:
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
index 2f72fd4b661f..72ab35cefe6c 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
@@ -128,18 +128,23 @@ public class DefaultErrorRegistry extends 
EventNotifierSupport implements ErrorR
         }
         String endpointUri = 
exchange.getProperty(ExchangePropertyKey.FAILURE_ENDPOINT, String.class);
 
-        // capture node id and location from the last message history entry
-        // (the historyNodeId on the exchange extension is cleared after the 
node finishes processing,
-        // so by the time the error event fires it is always null)
-        String toNode = null;
-        String location = null;
-        List<MessageHistory> history
-                = exchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, 
List.class);
-        if (history != null && !history.isEmpty()) {
-            MessageHistory last = history.get(history.size() - 1);
-            if (last.getNode() != null) {
-                toNode = last.getNode().getId();
-                location = 
LoggerHelper.getLineNumberLoggerName(last.getNode());
+        // capture node id and location where the exchange actually failed
+        // (captured up-front by the error handler / doCatch, before any 
failure processor such as
+        // onException or a dead letter channel ran its own processing steps - 
otherwise those steps
+        // would also be recorded in the message history, and the last entry 
would no longer point to
+        // the node that actually failed)
+        String toNode = 
exchange.getProperty(ExchangePropertyKey.FAILURE_NODE_ID, String.class);
+        String location = 
exchange.getProperty(ExchangePropertyKey.FAILURE_LOCATION, String.class);
+        if (toNode == null) {
+            // fallback for error handlers that do not capture the failure 
origin up-front (such as
+            // noErrorHandler): derive it from the last message history entry 
instead
+            List<MessageHistory> history = 
exchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, List.class);
+            if (history != null && !history.isEmpty()) {
+                MessageHistory last = history.get(history.size() - 1);
+                if (last.getNode() != null) {
+                    toNode = last.getNode().getId();
+                    location = 
LoggerHelper.getLineNumberLoggerName(last.getNode());
+                }
             }
         }
 
diff --git 
a/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc
 
b/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc
index 132bceab0b0e..37d1a28e2fbc 100644
--- 
a/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc
+++ 
b/core/camel-core-engine/src/main/docs/modules/eips/pages/dead-letter-channel.adoc
@@ -633,6 +633,26 @@ The `Exchange.FAILURE_ROUTE_ID` have the constant value 
`CamelFailureRouteId`.
 This allows, for example, you to fetch this information in your dead letter 
queue and use that for error reporting.
 
 
+=== Which node failed?
+
+Camel also stores the id of the node (EIP processor) where the message failed, 
along with its source
+code location (requires source location to be enabled). This information can 
be obtained in Java via:
+
+._Java-only: accessing the failure node id and location from the Exchange_
+[source,java]
+----
+String failedNodeId = exchange.getProperty(Exchange.FAILURE_NODE_ID, 
String.class);
+String failedLocation = exchange.getProperty(Exchange.FAILURE_LOCATION, 
String.class);
+----
+
+The `Exchange.FAILURE_NODE_ID` have the constant value `CamelFailureNodeId`, 
and `Exchange.FAILURE_LOCATION`
+have the constant value `CamelFailureLocation`.
+
+This information is captured up-front, as soon as the exception is caught, so 
it always points to the node
+that actually failed - even when the error handler (`onException`, `doCatch`, 
or the dead letter channel
+itself) performs further processing of its own before the exchange is moved to 
the dead letter queue.
+
+
 === Control if redelivery is allowed during stopping/shutdown
 
 The option `allowRedeliveryWhileStopping` (default is `true`) controls whether 
redelivery is allowed or not,
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json
index fe1bcc91fd59..b1a537a05ede 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doCatch.json
@@ -23,6 +23,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json
index d8b5516eb41e..0ece8a5dcdc7 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/doFinally.json
@@ -21,6 +21,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/errorHandler.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/errorHandler.json
index bea634af9bb9..12e4b0912735 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/errorHandler.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/errorHandler.json
@@ -18,6 +18,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json
index 1063517cde30..355a81d775dc 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/onException.json
@@ -32,6 +32,8 @@
   "exchangeProperties": {
     "CamelExceptionCaught": { "index": 0, "kind": "exchangeProperty", 
"displayName": "Exception Caught", "label": "producer", "required": false, 
"javaType": "java.lang.Exception", "deprecated": false, "autowired": false, 
"secret": false, "description": "Stores the caught exception due to a 
processing error of the current Exchange" },
     "CamelFailureEndpoint": { "index": 1, "kind": "exchangeProperty", 
"displayName": "Failure Endpoint", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Endpoint URI where the Exchange failed during processing" },
-    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" }
+    "CamelFailureRouteId": { "index": 2, "kind": "exchangeProperty", 
"displayName": "Failure Route Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Route ID where the Exchange failed during processing" },
+    "CamelFailureNodeId": { "index": 3, "kind": "exchangeProperty", 
"displayName": "Failure Node Id", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Node ID where the Exchange failed during processing" },
+    "CamelFailureLocation": { "index": 4, "kind": "exchangeProperty", 
"displayName": "Failure Location", "label": "producer", "required": false, 
"javaType": "String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Source code location where the Exchange failed during 
processing" }
   }
 }
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/CatchProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/CatchProcessor.java
index 8a3bed1e1b0f..30bf2a219b1a 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/CatchProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/CatchProcessor.java
@@ -154,11 +154,6 @@ public class CatchProcessor extends 
BaseDelegateProcessorSupport
         if (exchange.getProperty(ExchangePropertyKey.FAILURE_ENDPOINT) == 
null) {
             exchange.setProperty(ExchangePropertyKey.FAILURE_ENDPOINT, 
exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
         }
-        // and store the route id so we know in which route we failed
-        String routeId = ExchangeHelper.getAtRouteId(exchange);
-        if (routeId != null) {
-            exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID, 
routeId);
-        }
         // give the rest of the pipeline another chance
         exchange.setProperty(ExchangePropertyKey.EXCEPTION_HANDLED, true);
         exchange.setProperty(ExchangePropertyKey.EXCEPTION_CAUGHT, e);
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/FinallyProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/FinallyProcessor.java
index 26c71d900105..60ace585b1c4 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/FinallyProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/FinallyProcessor.java
@@ -58,11 +58,6 @@ public class FinallyProcessor extends 
BaseDelegateProcessorSupport
                 exchange.setProperty(ExchangePropertyKey.FAILURE_ENDPOINT,
                         exchange.getProperty(ExchangePropertyKey.TO_ENDPOINT));
             }
-            // and store the route id so we know in which route we failed
-            String routeId = ExchangeHelper.getAtRouteId(exchange);
-            if (routeId != null) {
-                exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID, 
routeId);
-            }
         }
 
         // continue processing
@@ -132,6 +127,8 @@ public class FinallyProcessor extends 
BaseDelegateProcessorSupport
                 if (exception == null) {
                     
exchange.removeProperty(ExchangePropertyKey.FAILURE_ENDPOINT);
                     
exchange.removeProperty(ExchangePropertyKey.FAILURE_ROUTE_ID);
+                    
exchange.removeProperty(ExchangePropertyKey.FAILURE_NODE_ID);
+                    
exchange.removeProperty(ExchangePropertyKey.FAILURE_LOCATION);
                 } else {
                     // set exception back on exchange
                     exchange.setException(exception);
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TryProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TryProcessor.java
index 26dfe7b90e4e..af2c5ad10026 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TryProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TryProcessor.java
@@ -92,6 +92,7 @@ public class TryProcessor extends BaseProcessorSupport
         final AsyncCallback callback;
         final Iterator<Processor> processors;
         final Object lastHandled;
+        boolean failureOriginCaptured;
 
         public TryState(Exchange exchange, AsyncCallback callback) {
             this.exchange = exchange;
@@ -104,6 +105,14 @@ public class TryProcessor extends BaseProcessorSupport
         @Override
         public void run() {
             if (continueRouting(processors, exchange)) {
+                // capture where the exchange failed as soon as the exception 
appears, before handing off to a
+                // doCatch/doFinally clause which is itself a channeled node 
and would otherwise add its own
+                // entry to the message history, hiding the node that actually 
failed
+                if (!failureOriginCaptured && exchange.getException() != null) 
{
+                    failureOriginCaptured = true;
+                    ExchangeHelper.captureFailureOrigin(exchange);
+                }
+
                 exchange.setProperty(ExchangePropertyKey.TRY_ROUTE_BLOCK, 
true);
                 ExchangeHelper.prepareOutToIn(exchange);
 
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/ShareUnitOfWorkAggregationStrategy.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/ShareUnitOfWorkAggregationStrategy.java
index 48b64d7a8b78..42b38ac12aa1 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/ShareUnitOfWorkAggregationStrategy.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/ShareUnitOfWorkAggregationStrategy.java
@@ -126,6 +126,14 @@ public final class ShareUnitOfWorkAggregationStrategy 
extends ServiceSupport imp
                 answer.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID,
                         
newExchange.getProperty(ExchangePropertyKey.FAILURE_ROUTE_ID));
             }
+            if (newExchange.getProperty(ExchangePropertyKey.FAILURE_NODE_ID) 
!= null) {
+                answer.setProperty(ExchangePropertyKey.FAILURE_NODE_ID,
+                        
newExchange.getProperty(ExchangePropertyKey.FAILURE_NODE_ID));
+            }
+            if (newExchange.getProperty(ExchangePropertyKey.FAILURE_LOCATION) 
!= null) {
+                answer.setProperty(ExchangePropertyKey.FAILURE_LOCATION,
+                        
newExchange.getProperty(ExchangePropertyKey.FAILURE_LOCATION));
+            }
             if (newExchange.getExchangeExtension().getErrorHandlerHandled() != 
null) {
                 answer.getExchangeExtension()
                         
.setErrorHandlerHandled(newExchange.getExchangeExtension().getErrorHandlerHandled());
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
index a4b1fba908b1..6e325518c49b 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
@@ -831,11 +831,9 @@ public abstract class RedeliveryErrorHandler extends 
ErrorHandlerSupport
                 exchange.setProperty(ExchangePropertyKey.EXCEPTION_CAUGHT, e);
             }
 
-            // store where the exception happened
-            Route rc = ExchangeHelper.getRoute(exchange);
-            if (rc != null) {
-                exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID, 
rc.getRouteId());
-            }
+            // store the route, node and location where the exception 
happened, before any failure processor
+            // (onException, dead letter channel, ...) runs and adds its own 
entries to the message history
+            ExchangeHelper.captureFailureOrigin(exchange);
         }
 
         /**
@@ -1381,11 +1379,9 @@ public abstract class RedeliveryErrorHandler extends 
ErrorHandlerSupport
 
             redeliveryCounter = incrementRedeliveryCounter(exchange);
 
-            // store where the exception happened
-            Route rc = ExchangeHelper.getRoute(exchange);
-            if (rc != null) {
-                exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID, 
rc.getRouteId());
-            }
+            // store the route, node and location where the exception 
happened, before any failure processor
+            // (onException, dead letter channel, ...) runs and adds its own 
entries to the message history
+            ExchangeHelper.captureFailureOrigin(exchange);
         }
 
         /**
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java 
b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java
index 847ae8702a41..c83ffe2c1741 100644
--- a/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java
@@ -27,6 +27,7 @@ import org.apache.camel.spi.ErrorRegistry;
 import org.apache.camel.spi.ErrorRegistryView;
 import org.junit.jupiter.api.Test;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -169,6 +170,36 @@ public class ErrorRegistryTest extends ContextTestSupport {
         assertNotNull(entry.getToNode(), "Node id should be captured for 
unhandled errors");
     }
 
+    @Test
+    public void testErrorRegistryHandledErrorReportsOriginNode() throws 
Exception {
+        getMockEndpoint("mock:handlerStep").expectedMessageCount(1);
+
+        template.sendBody("direct:handledWithSteps", "Hello World");
+
+        assertMockEndpointsSatisfied();
+
+        BacklogErrorEventMessage entry = 
context.getErrorRegistry().browse().iterator().next();
+        assertThat(entry.isHandled()).isTrue();
+        assertThat(entry.getToNode())
+                .as("toNode should point to the node that actually failed, not 
a node touched by the onException handler")
+                .isEqualTo("throwOrigin");
+    }
+
+    @Test
+    public void testErrorRegistryDoCatchReportsOriginNode() throws Exception {
+        getMockEndpoint("mock:catchStep").expectedMessageCount(1);
+
+        template.sendBody("direct:doCatchWithSteps", "Hello World");
+
+        assertMockEndpointsSatisfied();
+
+        BacklogErrorEventMessage entry = 
context.getErrorRegistry().browse().iterator().next();
+        assertThat(entry.isHandled()).isTrue();
+        assertThat(entry.getToNode())
+                .as("toNode should point to the node that actually failed, not 
a node touched by the doCatch block")
+                .isEqualTo("throwInTry");
+    }
+
     @Test
     public void testErrorRegistryCapturesEndpointUri() throws Exception {
         getMockEndpoint("mock:dead").expectedMessageCount(1);
@@ -298,6 +329,19 @@ public class ErrorRegistryTest extends ContextTestSupport {
                         .setProperty("myProp", constant("propValue"))
                         .setHeader("myHeader", constant("headerValue"))
                         .throwException(new IllegalArgumentException("Data 
error"));
+
+                from("direct:handledWithSteps").routeId("handledWithSteps")
+                        
.onException(IllegalArgumentException.class).handled(true).to("mock:handlerStep").end()
+                        .to("mock:before")
+                        .throwException(new IllegalArgumentException("Handled 
with steps")).id("throwOrigin");
+
+                from("direct:doCatchWithSteps").routeId("doCatchWithSteps")
+                        .doTry()
+                            .to("mock:before")
+                            .throwException(new 
IllegalArgumentException("Handled in doCatch")).id("throwInTry")
+                        .doCatch(IllegalArgumentException.class)
+                            .to("mock:catchStep")
+                        .end();
             }
         };
     }
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java 
b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
index 2eaab1737d4a..1f4e32d81067 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
@@ -1089,6 +1089,35 @@ public final class ExchangeHelper {
         return uow != null ? uow.getRoute() : null;
     }
 
+    /**
+     * Captures where the Exchange failed - the route id, node id, and source 
location - as the
+     * {@link Exchange#FAILURE_ROUTE_ID}, {@link Exchange#FAILURE_NODE_ID} and 
{@link Exchange#FAILURE_LOCATION}
+     * properties, based on the current route and the last entry in the 
message history.
+     * <p/>
+     * This must be called as soon as the exception is caught (handled or 
not), before any exception handler
+     * (onException, doCatch, dead letter channel, ...) has a chance to run 
any of its own processing steps - otherwise
+     * those steps would also be recorded in the message history, and the last 
entry would no longer point to the node
+     * that actually failed.
+     *
+     * @param exchange the exchange that failed
+     */
+    @SuppressWarnings("unchecked")
+    public static void captureFailureOrigin(Exchange exchange) {
+        Route rc = getRoute(exchange);
+        if (rc != null) {
+            exchange.setProperty(ExchangePropertyKey.FAILURE_ROUTE_ID, 
rc.getRouteId());
+        }
+        List<MessageHistory> history = 
exchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, List.class);
+        if (history != null && !history.isEmpty()) {
+            MessageHistory last = history.get(history.size() - 1);
+            if (last.getNode() != null) {
+                exchange.setProperty(ExchangePropertyKey.FAILURE_NODE_ID, 
last.getNode().getId());
+                exchange.setProperty(ExchangePropertyKey.FAILURE_LOCATION,
+                        LoggerHelper.getLineNumberLoggerName(last.getNode()));
+            }
+        }
+    }
+
     /**
      * Sets the body in message in the exchange taking the exchange pattern 
into consideration. If the pattern is out
      * capable, then the body is set outbound message. Otherwise it is set on 
the inbound message.

Reply via email to