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

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


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new a7c9e1ca2977 [backport camel-4.22.x] CAMEL-24513: camel-core - Error 
registry for handled exception should point to origin of error
a7c9e1ca2977 is described below

commit a7c9e1ca297748a0908361594e4886b753a5ee27
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Aug 28 08:37:48 2026 +0200

    [backport camel-4.22.x] CAMEL-24513: camel-core - Error registry for 
handled exception should point to origin of error
    
    Cherry-pick of #25786 onto camel-4.22.x.
    
    Co-authored-by: Claude Sonnet 5 <[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 64dfaee08fb5..56a3f034538e 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 64dfaee08fb5..56a3f034538e 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