You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2022/04/13 15:29:07 UTC

[camel] 01/02: CAMEL-16834: error handler in DSL model

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

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

commit 81c32ebe7af5624d0f29722297363a5498b65fb3
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Apr 13 16:42:52 2022 +0200

    CAMEL-16834: error handler in DSL model
---
 .../camel/catalog/models/routeConfiguration.json   |   1 +
 .../apache/camel/catalog/schemas/camel-spring.xsd  |   1 +
 .../org/apache/camel/model/routeConfiguration.json |   1 +
 .../camel/model/RouteConfigurationDefinition.java  |  40 +++++-
 .../apache/camel/model/RouteDefinitionHelper.java  |  24 +++-
 .../org/apache/camel/model/RoutesDefinition.java   |  24 +++-
 .../core/xml/AbstractCamelContextFactoryBean.java  |  11 +-
 .../model/RoutesConfigurationErrorHandlerTest.java | 160 +++++++++++++++++++++
 .../java/org/apache/camel/xml/in/ModelParser.java  |   1 +
 .../modules/ROOT/pages/route-configuration.adoc    |  27 ++++
 .../RouteConfigurationDefinitionDeserializer.java  |   7 +
 .../src/generated/resources/camel-yaml-dsl.json    |   3 +
 .../src/generated/resources/camelYamlDsl.json      |   3 +
 .../camel/dsl/yaml/RouteConfigurationTest.groovy   |  42 ++++++
 14 files changed, 334 insertions(+), 11 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/routeConfiguration.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/routeConfiguration.json
index a1b45b6bb7c..5e560cdfaad 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/routeConfiguration.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/routeConfiguration.json
@@ -12,6 +12,7 @@
     "output": false
   },
   "properties": {
+    "errorHandler": { "kind": "element", "displayName": "Error Handler", "required": false, "type": "object", "javaType": "org.apache.camel.model.ErrorHandlerDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the error handler to use, for routes that has not already been configured with an error handler." },
     "intercept": { "kind": "element", "displayName": "Intercept", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a route for an interceptor that intercepts every processing step." },
     "interceptFrom": { "kind": "element", "displayName": "Intercept From", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptFromDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a route for an interceptor that intercepts incoming messages on the given endpoint." },
     "interceptSendToEndpoint": { "kind": "element", "displayName": "Intercept Send To Endpoint", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptSendToEndpointDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Applies a route for an interceptor if an exchange is send to the given endpoint" },
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index 72e6d5fa578..0c731250a98 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -10535,6 +10535,7 @@ Reference to the route templates in the xml dsl.
     <xs:complexContent>
       <xs:extension base="tns:optionalIdentifiedDefinition">
         <xs:sequence>
+          <xs:element minOccurs="0" ref="tns:errorHandler"/>
           <xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:intercept"/>
           <xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:interceptFrom"/>
           <xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:interceptSendToEndpoint"/>
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeConfiguration.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeConfiguration.json
index a1b45b6bb7c..5e560cdfaad 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeConfiguration.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeConfiguration.json
@@ -12,6 +12,7 @@
     "output": false
   },
   "properties": {
+    "errorHandler": { "kind": "element", "displayName": "Error Handler", "required": false, "type": "object", "javaType": "org.apache.camel.model.ErrorHandlerDefinition", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the error handler to use, for routes that has not already been configured with an error handler." },
     "intercept": { "kind": "element", "displayName": "Intercept", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a route for an interceptor that intercepts every processing step." },
     "interceptFrom": { "kind": "element", "displayName": "Intercept From", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptFromDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a route for an interceptor that intercepts incoming messages on the given endpoint." },
     "interceptSendToEndpoint": { "kind": "element", "displayName": "Intercept Send To Endpoint", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.InterceptSendToEndpointDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Applies a route for an interceptor if an exchange is send to the given endpoint" },
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteConfigurationDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteConfigurationDefinition.java
index 1a7b65f59a1..0d509cef27d 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteConfigurationDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteConfigurationDefinition.java
@@ -26,6 +26,8 @@ import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 
+import org.apache.camel.ErrorHandlerFactory;
+import org.apache.camel.model.errorhandler.ErrorHandlerRefDefinition;
 import org.apache.camel.spi.Metadata;
 
 /**
@@ -37,8 +39,8 @@ import org.apache.camel.spi.Metadata;
 public class RouteConfigurationDefinition extends OptionalIdentifiedDefinition<RouteConfigurationDefinition>
         implements PreconditionContainer {
 
-    // TODO: Model for ErrorHandler (requires to move error handler model from spring-xml, blueprint to core)
-
+    @XmlElement
+    private ErrorHandlerDefinition errorHandler;
     @XmlElement(name = "intercept")
     private List<InterceptDefinition> intercepts = new ArrayList<>();
     @XmlElement(name = "interceptFrom")
@@ -71,6 +73,14 @@ public class RouteConfigurationDefinition extends OptionalIdentifiedDefinition<R
         return "RoutesConfiguration " + getId();
     }
 
+    public ErrorHandlerDefinition getErrorHandler() {
+        return errorHandler;
+    }
+
+    public void setErrorHandler(ErrorHandlerDefinition errorHandler) {
+        this.errorHandler = errorHandler;
+    }
+
     public List<OnExceptionDefinition> getOnExceptions() {
         return onExceptions;
     }
@@ -132,6 +142,32 @@ public class RouteConfigurationDefinition extends OptionalIdentifiedDefinition<R
     // Fluent API
     // -------------------------------------------------------------------------
 
+    /**
+     * Sets the error handler to use, for routes that has not already been configured with an error handler.
+     *
+     * @param  ref reference to existing error handler
+     * @return     the builder
+     */
+    public RouteConfigurationDefinition errorHandler(String ref) {
+        ErrorHandlerDefinition def = new ErrorHandlerDefinition();
+        def.setErrorHandlerType(new ErrorHandlerRefDefinition(ref));
+        setErrorHandler(def);
+        return this;
+    }
+
+    /**
+     * Sets the error handler to use, for routes that has not already been configured with an error handler.
+     *
+     * @param  errorHandler the error handler
+     * @return              the builder
+     */
+    public RouteConfigurationDefinition errorHandler(ErrorHandlerFactory errorHandler) {
+        ErrorHandlerDefinition def = new ErrorHandlerDefinition();
+        def.setErrorHandlerType(errorHandler);
+        setErrorHandler(def);
+        return this;
+    }
+
     /**
      * Sets the predicate of the precondition in simple language to evaluate in order to determine if this route
      * configuration should be included or not.
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
index 98b9d1923f5..e1d072d2a29 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
@@ -307,7 +307,7 @@ public final class RouteDefinitionHelper {
      * @param route   the route
      */
     public static void prepareRoute(CamelContext context, RouteDefinition route) {
-        prepareRoute(context, route, null, null, null, null, null);
+        prepareRoute(context, route, null, null, null, null, null, null);
     }
 
     /**
@@ -317,6 +317,7 @@ public final class RouteDefinitionHelper {
      *
      * @param context                            the camel context
      * @param route                              the route
+     * @param errorHandler                       optional error handler
      * @param onExceptions                       optional list of onExceptions
      * @param intercepts                         optional list of interceptors
      * @param interceptFromDefinitions           optional list of interceptFroms
@@ -324,13 +325,16 @@ public final class RouteDefinitionHelper {
      * @param onCompletions                      optional list onCompletions
      */
     public static void prepareRoute(
-            CamelContext context, RouteDefinition route, List<OnExceptionDefinition> onExceptions,
+            CamelContext context, RouteDefinition route,
+            ErrorHandlerDefinition errorHandler,
+            List<OnExceptionDefinition> onExceptions,
             List<InterceptDefinition> intercepts,
             List<InterceptFromDefinition> interceptFromDefinitions,
             List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions,
             List<OnCompletionDefinition> onCompletions) {
 
-        prepareRouteImp(context, route, onExceptions, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions,
+        prepareRouteImp(context, route, errorHandler, onExceptions, intercepts, interceptFromDefinitions,
+                interceptSendToEndpointDefinitions,
                 onCompletions);
     }
 
@@ -341,6 +345,7 @@ public final class RouteDefinitionHelper {
      *
      * @param context                            the camel context
      * @param route                              the route
+     * @param errorHandler                       optional error handler
      * @param onExceptions                       optional list of onExceptions
      * @param intercepts                         optional list of interceptors
      * @param interceptFromDefinitions           optional list of interceptFroms
@@ -348,7 +353,9 @@ public final class RouteDefinitionHelper {
      * @param onCompletions                      optional list onCompletions
      */
     private static void prepareRouteImp(
-            CamelContext context, RouteDefinition route, List<OnExceptionDefinition> onExceptions,
+            CamelContext context, RouteDefinition route,
+            ErrorHandlerDefinition errorHandler,
+            List<OnExceptionDefinition> onExceptions,
             List<InterceptDefinition> intercepts,
             List<InterceptFromDefinition> interceptFromDefinitions,
             List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions,
@@ -370,7 +377,7 @@ public final class RouteDefinitionHelper {
         RouteDefinitionHelper.prepareRouteForInit(route, abstracts, lower);
 
         // parent and error handler builder should be initialized first
-        initParentAndErrorHandlerBuilder(context, route, abstracts, onExceptions);
+        initParentAndErrorHandlerBuilder(context, route, errorHandler, abstracts, onExceptions);
         // validate top-level violations
         validateTopLevel(route.getOutputs());
         // then interceptors
@@ -440,10 +447,13 @@ public final class RouteDefinitionHelper {
     }
 
     private static void initParentAndErrorHandlerBuilder(
-            CamelContext context, RouteDefinition route, List<ProcessorDefinition<?>> abstracts,
+            CamelContext context, RouteDefinition route, ErrorHandlerDefinition errorHandler,
+            List<ProcessorDefinition<?>> abstracts,
             List<OnExceptionDefinition> onExceptions) {
 
-        if (context != null) {
+        if (errorHandler != null) {
+            route.setErrorHandlerFactoryIfNull(errorHandler.getErrorHandlerType());
+        } else if (context != null) {
             // let the route inherit the error handler builder from camel
             // context if none already set
 
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
index f53235dc458..7c582c65bfb 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
@@ -18,6 +18,7 @@ package org.apache.camel.model;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -241,6 +242,7 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit
         route.setResource(resource);
 
         // merge global and route scoped together
+        final AtomicReference<ErrorHandlerDefinition> gcErrorHandler = new AtomicReference<>();
         List<OnExceptionDefinition> oe = new ArrayList<>(onExceptions);
         List<InterceptDefinition> icp = new ArrayList<>(intercepts);
         List<InterceptFromDefinition> ifrom = new ArrayList<>(interceptFroms);
@@ -250,6 +252,7 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit
             List<RouteConfigurationDefinition> globalConfigurations
                     = getCamelContext().adapt(ModelCamelContext.class).getRouteConfigurationDefinitions();
             if (globalConfigurations != null) {
+
                 // if there are multiple ids configured then we should apply in that same order
                 String[] ids = route.getRouteConfigurationId() != null
                         ? route.getRouteConfigurationId().split(",") : new String[] { "*" };
@@ -266,6 +269,12 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit
                                 }
                             })
                             .forEach(g -> {
+                                // there can only be one global error handler, so override previous, meaning
+                                // that we will pick the last in the sort (take precedence)
+                                if (g.getErrorHandler() != null) {
+                                    gcErrorHandler.set(g.getErrorHandler());
+                                }
+
                                 String aid = g.getId() == null ? "<default>" : g.getId();
                                 // remember the id that was used on the route
                                 route.addAppliedRouteConfigurationId(aid);
@@ -276,11 +285,24 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit
                                 oc.addAll(g.getOnCompletions());
                             });
                 }
+
+                // set error handler before prepare
+                if (errorHandlerFactory == null && gcErrorHandler.get() != null) {
+                    ErrorHandlerDefinition ehd = gcErrorHandler.get();
+                    route.setErrorHandlerFactoryIfNull(ehd.getErrorHandlerType());
+                }
             }
         }
 
+        // if the route does not already have an error handler set then use route configured error handler
+        // if one was configured
+        ErrorHandlerDefinition ehd = null;
+        if (errorHandlerFactory == null && gcErrorHandler.get() != null) {
+            ehd = gcErrorHandler.get();
+        }
+
         // must prepare the route before we can add it to the routes list
-        RouteDefinitionHelper.prepareRoute(getCamelContext(), route, oe, icp, ifrom, ito, oc);
+        RouteDefinitionHelper.prepareRoute(getCamelContext(), route, ehd, oe, icp, ifrom, ito, oc);
 
         if (LOG.isDebugEnabled() && route.getAppliedRouteConfigurationIds() != null) {
             LOG.debug("Route: {} is using route configurations ids: {}", route.getId(),
diff --git a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java
index adbd809e0c9..b9238c769fa 100644
--- a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java
+++ b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java
@@ -25,6 +25,7 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -57,6 +58,7 @@ import org.apache.camel.impl.engine.DefaultManagementStrategy;
 import org.apache.camel.impl.engine.TransformerKey;
 import org.apache.camel.impl.engine.ValidatorKey;
 import org.apache.camel.model.ContextScanDefinition;
+import org.apache.camel.model.ErrorHandlerDefinition;
 import org.apache.camel.model.FaultToleranceConfigurationDefinition;
 import org.apache.camel.model.FromDefinition;
 import org.apache.camel.model.GlobalOptionsDefinition;
@@ -590,6 +592,7 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex
             route.resetPrepare();
 
             // merge global and route scoped together
+            AtomicReference<ErrorHandlerDefinition> errorHandler = new AtomicReference<>();
             List<OnExceptionDefinition> oe = new ArrayList<>(getOnExceptions());
             List<InterceptDefinition> icp = new ArrayList<>(getIntercepts());
             List<InterceptFromDefinition> ifrom = new ArrayList<>(getInterceptFroms());
@@ -615,6 +618,12 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex
                                     }
                                 })
                                 .forEach(g -> {
+                                    // there can only be one global error handler, so override previous, meaning
+                                    // that we will pick the last in the sort (take precedence)
+                                    if (g.getErrorHandler() != null) {
+                                        errorHandler.set(g.getErrorHandler());
+                                    }
+
                                     String aid = g.getId() == null ? "<default>" : g.getId();
                                     // remember the id that was used on the route
                                     route.addAppliedRouteConfigurationId(aid);
@@ -629,7 +638,7 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex
             }
 
             // must prepare the route before we can add it to the routes list
-            RouteDefinitionHelper.prepareRoute(getContext(), route, oe, icp, ifrom, ito, oc);
+            RouteDefinitionHelper.prepareRoute(getContext(), route, errorHandler.get(), oe, icp, ifrom, ito, oc);
 
             if (LOG.isDebugEnabled() && route.getAppliedRouteConfigurationIds() != null) {
                 LOG.debug("Route: {} is using route configurations ids: {}", route.getId(),
diff --git a/core/camel-core/src/test/java/org/apache/camel/model/RoutesConfigurationErrorHandlerTest.java b/core/camel-core/src/test/java/org/apache/camel/model/RoutesConfigurationErrorHandlerTest.java
new file mode 100644
index 00000000000..0dcc4ec67b7
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/model/RoutesConfigurationErrorHandlerTest.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.model;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.builder.RouteConfigurationBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Fail.fail;
+
+public class RoutesConfigurationErrorHandlerTest extends ContextTestSupport {
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    public void testGlobal() throws Exception {
+        context.addRoutes(new RouteConfigurationBuilder() {
+            @Override
+            public void configuration() throws Exception {
+                // global routes configuration
+                routeConfiguration().errorHandler(deadLetterChannel("mock:error"));
+
+            }
+        });
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .throwException(new IllegalArgumentException("Foo"));
+
+                from("direct:start2")
+                        .throwException(new IllegalArgumentException("Foo2"));
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:error").expectedBodiesReceived("Hello World", "Bye World");
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start2", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testLocalOverride() throws Exception {
+        context.addRoutes(new RouteConfigurationBuilder() {
+            @Override
+            public void configuration() throws Exception {
+                // global routes configuration
+                routeConfiguration().errorHandler(deadLetterChannel("mock:error"));
+
+            }
+        });
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .throwException(new IllegalArgumentException("Foo"));
+
+                from("direct:start2")
+                        .errorHandler(deadLetterChannel("mock:error2"))
+                        .throwException(new IllegalArgumentException("Foo2"));
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:error").expectedBodiesReceived("Hello World");
+        getMockEndpoint("mock:error2").expectedBodiesReceived("Bye World");
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start2", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testLocalConfiguration() throws Exception {
+        context.addRoutes(new RouteConfigurationBuilder() {
+            @Override
+            public void configuration() throws Exception {
+                routeConfiguration("mylocal").errorHandler(deadLetterChannel("mock:error"));
+
+            }
+        });
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .throwException(new IllegalArgumentException("Foo"));
+
+                from("direct:start2").routeConfigurationId("mylocal")
+                        .throwException(new IllegalArgumentException("Foo2"));
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:error").expectedBodiesReceived("Bye World");
+
+        try {
+            template.sendBody("direct:start", "Hello World");
+            fail("Should throw exception");
+        } catch (Exception e) {
+            // expected
+        }
+        template.sendBody("direct:start2", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testGlobalAndLocal() throws Exception {
+        context.addRoutes(new RouteConfigurationBuilder() {
+            @Override
+            public void configuration() throws Exception {
+                routeConfiguration().errorHandler(deadLetterChannel("mock:error"));
+                routeConfiguration("mylocal").errorHandler(deadLetterChannel("mock:error2"));
+
+            }
+        });
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .throwException(new IllegalArgumentException("Foo"));
+
+                from("direct:start2").routeConfigurationId("mylocal")
+                        .throwException(new IllegalArgumentException("Foo2"));
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:error").expectedBodiesReceived("Hello World");
+        getMockEndpoint("mock:error2").expectedBodiesReceived("Bye World");
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start2", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+}
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index 78c3d65df2a..fe6144cae44 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -978,6 +978,7 @@ public class ModelParser extends BaseParser {
             return optionalIdentifiedDefinitionAttributeHandler().accept(def, key, val);
         }, (def, key) -> {
             switch (key) {
+                case "errorHandler": def.setErrorHandler(doParseErrorHandlerDefinition()); break;
                 case "interceptFrom": doAdd(doParseInterceptFromDefinition(), def.getInterceptFroms(), def::setInterceptFroms); break;
                 case "interceptSendToEndpoint": doAdd(doParseInterceptSendToEndpointDefinition(), def.getInterceptSendTos(), def::setInterceptSendTos); break;
                 case "intercept": doAdd(doParseInterceptDefinition(), def.getIntercepts(), def::setIntercepts); break;
diff --git a/docs/user-manual/modules/ROOT/pages/route-configuration.adoc b/docs/user-manual/modules/ROOT/pages/route-configuration.adoc
index 2215ffb67f6..4a71d7c07c1 100644
--- a/docs/user-manual/modules/ROOT/pages/route-configuration.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-configuration.adoc
@@ -12,6 +12,7 @@ The route configuration is supported by all DSL's, so useable by: Java, XML, Gro
 
 In the route configuration you can setup common strategies for:
 
+- xref:error-handler.adoc[Error Handler]
 - xref:exception-clause.adoc[OnException]
 - xref:oncompletion.adoc[OnCompletion]
 - xref:components:eips:intercept.adoc[Intercept]
@@ -176,6 +177,32 @@ then fail and rollback.
 If you add more routes, then those routes can also be assigned the _retryError_ configuration
 if they should also retry in case of error.
 
+=== Route Configuration with Error Handler
+
+Each route configuration can also have a specific error handler configured, as shown below:
+
+[source,java]
+----
+public class MyJavaErrorHandler extends RouteConfigurationBuilder {
+
+    @Override
+    public void configuration() throws Exception {
+        routeConfiguration()
+            .errorHandler(deadLetterChannel("mock:dead"));
+
+        routeConfiguration("retryError")
+            .onException(Exception.class).maximumRedeliveries(5);
+    }
+}
+----
+
+In the example above, the _nameless_ configuration has an error handler with a dead letter queue.
+And the route configuration with id _retryError_ does not, and instead it will attempt
+to retry the failing message up till 5 times before giving up (exhausted). Because this
+route configuration does not have any error handler assigned, then Camel will use the default error handler.
+
+IMPORTANT: Routes that have a local error handler defined, will always use this error handler,
+instead of the error handler from route configurations. A route can only have 1 error handler.
 
 == Route Configuration in XML
 
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteConfigurationDefinitionDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteConfigurationDefinitionDeserializer.java
index 5b99d26a339..1123731de60 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteConfigurationDefinitionDeserializer.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteConfigurationDefinitionDeserializer.java
@@ -20,6 +20,7 @@ import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
 import org.apache.camel.dsl.yaml.common.YamlDeserializerBase;
 import org.apache.camel.dsl.yaml.common.YamlDeserializerResolver;
 import org.apache.camel.dsl.yaml.common.exception.UnsupportedFieldException;
+import org.apache.camel.model.ErrorHandlerDefinition;
 import org.apache.camel.model.InterceptDefinition;
 import org.apache.camel.model.InterceptFromDefinition;
 import org.apache.camel.model.InterceptSendToEndpointDefinition;
@@ -41,6 +42,7 @@ import org.snakeyaml.engine.v2.nodes.SequenceNode;
           properties = {
                   @YamlProperty(name = "id", type = "string"),
                   @YamlProperty(name = "precondition", type = "string"),
+                  @YamlProperty(name = "error-handler", type = "object:org.apache.camel.model.ErrorHandlerDefinition.class"),
                   @YamlProperty(name = "intercept", type = "array:org.apache.camel.model.InterceptDefinition"),
                   @YamlProperty(name = "intercept-from", type = "array:org.apache.camel.model.InterceptFromDefinition"),
                   @YamlProperty(name = "intercept-send-to-endpoint",
@@ -79,6 +81,11 @@ public class RouteConfigurationDefinitionDeserializer extends YamlDeserializerBa
                     case "precondition":
                         target.setPrecondition(asText(val));
                         break;
+                    case "error-handler":
+                        setDeserializationContext(val, dc);
+                        ErrorHandlerDefinition ehd = asType(val, ErrorHandlerDefinition.class);
+                        target.setErrorHandler(ehd);
+                        break;
                     case "on-exception":
                         setDeserializationContext(val, dc);
                         OnExceptionDefinition oed = asType(val, OnExceptionDefinition.class);
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
index 24e65a97bef..260ec1996de 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
@@ -2324,6 +2324,9 @@
         }, {
           "type" : "object",
           "properties" : {
+            "error-handler" : {
+              "$ref" : "#/items/definitions/org.apache.camel.model.ErrorHandlerDefinition.class"
+            },
             "id" : {
               "type" : "string"
             },
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json
index ac348b5ee5f..62c0864d508 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camelYamlDsl.json
@@ -2228,6 +2228,9 @@
         }, {
           "type" : "object",
           "properties" : {
+            "errorHandler" : {
+              "$ref" : "#/items/definitions/org.apache.camel.model.ErrorHandlerDefinition.class"
+            },
             "id" : {
               "type" : "string"
             },
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteConfigurationTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteConfigurationTest.groovy
index 04ff381cb6e..bba80b12c80 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteConfigurationTest.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteConfigurationTest.groovy
@@ -223,4 +223,46 @@ class RouteConfigurationTest extends YamlTestSupport {
         Assertions.assertTrue(out2.isFailed())
     }
 
+    def "route-configuration-error-handler"() {
+        setup:
+        // global configurations
+        loadRoutes """
+                - beans:
+                  - name: myFailingProcessor
+                    type: ${MyFailingProcessor.name}
+                - route-configuration:
+                    - error-handler:
+                        dead-letter-channel: 
+                          dead-letter-uri: "mock:on-error"
+            """
+        // routes
+        loadRoutes """
+                - from:
+                    uri: "direct:start"
+                    steps:
+                      - process: 
+                          ref: "myFailingProcessor"            
+                - from:
+                    uri: "direct:start2"
+                    steps:
+                      - process: 
+                          ref: "myFailingProcessor"            
+            """
+
+        withMock('mock:on-error') {
+            expectedBodiesReceived 'hello', 'hello2'
+        }
+
+        when:
+        context.start()
+
+        withTemplate {
+            to('direct:start').withBody('hello').send()
+            to('direct:start2').withBody('hello2').send()
+        }
+        then:
+        MockEndpoint.assertIsSatisfied(context)
+    }
+
+
 }