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

gnodet pushed a commit to branch CAMEL-23079/error-registry
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 4feda3e37163d53e018692a1e3c7a870feab7782
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri Mar 6 10:40:13 2026 +0100

    CAMEL-23079: Add ErrorRegistry SPI for capturing routing errors
    
    Add a new ErrorRegistry service that captures exceptions during message
    routing and stores immutable snapshots in bounded in-memory storage.
    
    - ErrorRegistry SPI with ErrorRegistryEntry and ErrorRegistryView interfaces
    - DefaultErrorRegistry implementation using EventNotifier pattern
    - Configurable max entries, TTL, and optional stack trace capture
    - Route-scoped views via forRoute(routeId)
    - JMX MBean for management and monitoring
    - Dev Console for browsing errors
    - Configuration properties (camel.main.errorRegistry*)
    - Disabled by default, opt-in via setEnabled(true)
---
 .../apache/camel/catalog/dev-consoles.properties   |   1 +
 .../apache/camel/catalog/dev-consoles/errors.json  |  15 ++
 .../main/camel-main-configuration-metadata.json    |   4 +
 .../main/java/org/apache/camel/CamelContext.java   |  15 ++
 .../java/org/apache/camel/spi/ErrorRegistry.java   |  96 +++++++
 .../org/apache/camel/spi/ErrorRegistryEntry.java   |  69 +++++
 .../org/apache/camel/spi/ErrorRegistryView.java    |  47 ++++
 .../camel/impl/engine/AbstractCamelContext.java    |  19 ++
 .../impl/engine/DefaultCamelContextExtension.java  |  20 ++
 .../camel/impl/engine/DefaultErrorRegistry.java    | 289 +++++++++++++++++++++
 .../camel/impl/engine/SimpleCamelContext.java      |   6 +
 .../org/apache/camel/dev-console/errors.json       |  15 ++
 .../services/org/apache/camel/dev-console/errors   |   2 +
 .../org/apache/camel/dev-consoles.properties       |   2 +-
 .../camel/impl/console/ErrorRegistryConsole.java   | 134 ++++++++++
 .../apache/camel/impl/CamelContextConfigurer.java  |   6 +
 .../org/apache/camel/impl/ErrorRegistryTest.java   | 174 +++++++++++++
 .../MainConfigurationPropertiesConfigurer.java     |  28 ++
 .../camel-main-configuration-metadata.json         |   4 +
 core/camel-main/src/main/docs/main.adoc            |   6 +-
 .../camel/main/DefaultConfigurationConfigurer.java |   6 +
 .../camel/main/DefaultConfigurationProperties.java |  95 +++++++
 .../api/management/mbean/CamelOpenMBeanTypes.java  |  19 ++
 .../mbean/ManagedErrorRegistryMBean.java           |  64 +++++
 .../management/JmxManagementLifecycleStrategy.java |   4 +
 .../management/mbean/ManagedErrorRegistry.java     | 140 ++++++++++
 26 files changed, 1278 insertions(+), 2 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
index 6954ed2b5d4e..db039fb9379c 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
@@ -10,6 +10,7 @@ consumer
 context
 debug
 endpoint
+errors
 eval-language
 event
 fault-tolerance
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/errors.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/errors.json
new file mode 100644
index 000000000000..62a53fcd97bc
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/errors.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "errors",
+    "title": "Error Registry",
+    "description": "Display captured routing errors",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.ErrorRegistryConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-console",
+    "version": "4.19.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index e0e8c6b15390..2656901d4cb7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -69,6 +69,10 @@
     { "name": "camel.main.endpointBridgeErrorHandler", "required": false, 
"description": "Allows for bridging the consumer to the Camel routing Error 
Handler, which mean any exceptions occurred while the consumer is trying to 
pickup incoming messages, or the likes, will now be processed as a message and 
handled by the routing Error Handler. By default the consumer will use the 
org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be 
logged at WARN\/ERROR level and igno [...]
     { "name": "camel.main.endpointLazyStartProducer", "required": false, 
"description": "Whether the producer should be started lazy (on the first 
message). By starting lazy you can use this to allow CamelContext and routes to 
startup in situations where a producer may otherwise fail during starting and 
cause the route to fail being started. By deferring this startup to be lazy 
then the startup failure can be handled during routing messages via Camel's 
routing error handlers. Beware that [...]
     { "name": "camel.main.endpointRuntimeStatisticsEnabled", "required": 
false, "description": "Sets whether endpoint runtime statistics is enabled 
(gathers runtime usage of each incoming and outgoing endpoints). The default 
value is false.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
+    { "name": "camel.main.errorRegistryEnabled", "required": false, 
"description": "Sets whether the error registry is enabled to capture errors 
during message routing. This is by default disabled.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
+    { "name": "camel.main.errorRegistryMaximumEntries", "required": false, 
"description": "Sets the maximum number of error entries to keep in the error 
registry. When the limit is exceeded, the oldest entries are evicted. The 
default value is 100.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 100, "secret": false },
+    { "name": "camel.main.errorRegistryStackTraceEnabled", "required": false, 
"description": "Sets whether to capture stack traces in the error registry. 
This is disabled by default to reduce memory usage.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
+    { "name": "camel.main.errorRegistryTimeToLiveSeconds", "required": false, 
"description": "Sets the time-to-live in seconds for error entries in the error 
registry. Entries older than this are evicted. The default value is 3600 (1 
hour).", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", 
"type": "integer", "javaType": "int", "defaultValue": 3600, "secret": false },
     { "name": "camel.main.exchangeFactory", "required": false, "description": 
"Controls whether to pool (reuse) exchanges or create new exchanges 
(prototype). Using pooled will reduce JVM garbage collection overhead by 
avoiding to re-create Exchange instances per message each consumer receives. 
The default is prototype mode.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "enum", 
"javaType": "java.lang.String", "defaultValue": "default", "secret": false, " 
[...]
     { "name": "camel.main.exchangeFactoryCapacity", "required": false, 
"description": "The capacity the pool (for each consumer) uses for storing 
exchanges. The default capacity is 100.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 100, "secret": false },
     { "name": "camel.main.exchangeFactoryStatisticsEnabled", "required": 
false, "description": "Configures whether statistics is enabled on exchange 
factory.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
diff --git a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java 
b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
index dd4e4ba78b63..e5921b3d2141 100644
--- a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
@@ -30,6 +30,7 @@ import org.apache.camel.spi.DataFormat;
 import org.apache.camel.spi.DataType;
 import org.apache.camel.spi.Debugger;
 import org.apache.camel.spi.EndpointRegistry;
+import org.apache.camel.spi.ErrorRegistry;
 import org.apache.camel.spi.ExecutorServiceManager;
 import org.apache.camel.spi.InflightRepository;
 import org.apache.camel.spi.Injector;
@@ -1228,6 +1229,20 @@ public interface CamelContext extends 
CamelContextLifecycle, RuntimeConfiguratio
      */
     void setInflightRepository(InflightRepository repository);
 
+    /**
+     * Gets the error registry
+     *
+     * @return the error registry
+     */
+    ErrorRegistry getErrorRegistry();
+
+    /**
+     * Sets a custom error registry to use
+     *
+     * @param errorRegistry the error registry
+     */
+    void setErrorRegistry(ErrorRegistry errorRegistry);
+
     /**
      * Gets the application CamelContext class loader which may be helpful for 
running camel in other containers
      *
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistry.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistry.java
new file mode 100644
index 000000000000..7f00f7ebe6ac
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistry.java
@@ -0,0 +1,96 @@
+/*
+ * 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.spi;
+
+import java.time.Duration;
+
+import org.apache.camel.StaticService;
+
+/**
+ * A registry which captures exceptions that occurred during message routing 
and stores them in memory.
+ * <p/>
+ * This is an opt-in feature that must be enabled. When enabled, the registry 
captures error snapshots (exception type,
+ * message, stack trace) without retaining references to the original exchange 
or exception objects.
+ * <p/>
+ * The registry has a configurable maximum capacity and time-to-live to 
prevent unbounded memory growth and stale data.
+ * <p/>
+ * The registry itself implements {@link ErrorRegistryView} for global scope, 
and scoped views for individual routes can
+ * be obtained via {@link #forRoute(String)}.
+ *
+ * @see ErrorRegistryEntry
+ * @see ErrorRegistryView
+ */
+public interface ErrorRegistry extends ErrorRegistryView, StaticService {
+
+    /**
+     * Gets a view scoped to a specific route.
+     * <p/>
+     * The returned view is a lightweight filter over the same underlying data.
+     *
+     * @param  routeId the route id
+     * @return         a view containing only errors from the given route
+     */
+    ErrorRegistryView forRoute(String routeId);
+
+    // -- Configuration --
+
+    /**
+     * Whether the error registry is enabled
+     */
+    boolean isEnabled();
+
+    /**
+     * Sets whether the error registry is enabled.
+     * <p/>
+     * This is by default disabled.
+     */
+    void setEnabled(boolean enabled);
+
+    /**
+     * The maximum number of error entries to keep in the registry
+     */
+    int getMaximumEntries();
+
+    /**
+     * Sets the maximum number of error entries to keep. When the limit is 
exceeded, the oldest entries are evicted.
+     * <p/>
+     * The default value is 100.
+     */
+    void setMaximumEntries(int maximumEntries);
+
+    /**
+     * The time-to-live for error entries
+     */
+    Duration getTimeToLive();
+
+    /**
+     * Sets the time-to-live for error entries. Entries older than this 
duration are evicted.
+     * <p/>
+     * The default value is 1 hour.
+     */
+    void setTimeToLive(Duration timeToLive);
+
+    /**
+     * Whether stack trace capture is enabled
+     */
+    boolean isStackTraceEnabled();
+
+    /**
+     * Sets whether to capture stack traces. This is disabled by default to 
reduce memory usage.
+     */
+    void setStackTraceEnabled(boolean stackTraceEnabled);
+}
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistryEntry.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistryEntry.java
new file mode 100644
index 000000000000..a48e62895032
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistryEntry.java
@@ -0,0 +1,69 @@
+/*
+ * 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.spi;
+
+import java.time.Instant;
+
+/**
+ * A snapshot of an error that occurred during message routing.
+ * <p/>
+ * This is an immutable value object that does not hold references to the 
original exchange or exception.
+ */
+public interface ErrorRegistryEntry {
+
+    /**
+     * The exchange id
+     */
+    String exchangeId();
+
+    /**
+     * The route id where the error occurred
+     */
+    String routeId();
+
+    /**
+     * The endpoint URI where the error occurred (if available)
+     */
+    String endpointUri();
+
+    /**
+     * The timestamp when the error occurred
+     */
+    Instant timestamp();
+
+    /**
+     * Whether the error was handled by an error handler or onException
+     */
+    boolean handled();
+
+    /**
+     * The fully qualified class name of the exception
+     */
+    String exceptionType();
+
+    /**
+     * The exception message
+     */
+    String exceptionMessage();
+
+    /**
+     * The stack trace lines, or {@code null} if stack trace capture is 
disabled.
+     * <p/>
+     * Each element represents one line of the stack trace.
+     */
+    String[] stackTrace();
+}
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistryView.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistryView.java
new file mode 100644
index 000000000000..50e8a470bc59
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ErrorRegistryView.java
@@ -0,0 +1,47 @@
+/*
+ * 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.spi;
+
+import java.util.Collection;
+
+/**
+ * A read-only view over error entries in an {@link ErrorRegistry}, supporting 
browsing and clearing.
+ */
+public interface ErrorRegistryView {
+
+    /**
+     * The number of error entries in this view
+     */
+    int size();
+
+    /**
+     * Browse all error entries, sorted by most recent first
+     */
+    Collection<ErrorRegistryEntry> browse();
+
+    /**
+     * Browse error entries with a limit, sorted by most recent first
+     *
+     * @param limit maximum number of entries to return
+     */
+    Collection<ErrorRegistryEntry> browse(int limit);
+
+    /**
+     * Clear all error entries in this view
+     */
+    void clear();
+}
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index 0af2db032ab9..888f62cd5dcc 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -116,6 +116,7 @@ import org.apache.camel.spi.DumpRoutesStrategy;
 import org.apache.camel.spi.EndpointRegistry;
 import org.apache.camel.spi.EndpointServiceRegistry;
 import org.apache.camel.spi.EndpointStrategy;
+import org.apache.camel.spi.ErrorRegistry;
 import org.apache.camel.spi.EventNotifier;
 import org.apache.camel.spi.ExchangeFactory;
 import org.apache.camel.spi.ExchangeFactoryManager;
@@ -2612,6 +2613,12 @@ public abstract class AbstractCamelContext extends 
BaseService
             addService(runtimeEndpointRegistry, true, true);
         }
 
+        // register error registry as event notifier so it captures exchange 
failure events
+        ErrorRegistry errorRegistry = getErrorRegistry();
+        if (errorRegistry instanceof EventNotifier && getManagementStrategy() 
!= null) {
+            getManagementStrategy().addEventNotifier((EventNotifier) 
errorRegistry);
+        }
+
         bindDataFormats();
 
         // init components
@@ -3871,6 +3878,16 @@ public abstract class AbstractCamelContext extends 
BaseService
         camelContextExtension.setInflightRepository(repository);
     }
 
+    @Override
+    public ErrorRegistry getErrorRegistry() {
+        return camelContextExtension.getErrorRegistry();
+    }
+
+    @Override
+    public void setErrorRegistry(ErrorRegistry errorRegistry) {
+        camelContextExtension.setErrorRegistry(errorRegistry);
+    }
+
     @Override
     public void setAutoStartup(Boolean autoStartup) {
         this.autoStartup = autoStartup;
@@ -4430,6 +4447,8 @@ public abstract class AbstractCamelContext extends 
BaseService
 
     protected abstract InflightRepository createInflightRepository();
 
+    protected abstract ErrorRegistry createErrorRegistry();
+
     protected abstract AsyncProcessorAwaitManager 
createAsyncProcessorAwaitManager();
 
     protected abstract RouteController createRouteController();
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
index 62be92e65c3d..9a78f6580a1c 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelContextExtension.java
@@ -46,6 +46,7 @@ import org.apache.camel.spi.ClassResolver;
 import org.apache.camel.spi.EndpointServiceRegistry;
 import org.apache.camel.spi.EndpointStrategy;
 import org.apache.camel.spi.EndpointUriFactory;
+import org.apache.camel.spi.ErrorRegistry;
 import org.apache.camel.spi.EventNotifier;
 import org.apache.camel.spi.ExchangeFactory;
 import org.apache.camel.spi.ExchangeFactoryManager;
@@ -127,6 +128,7 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
     private volatile MessageHistoryFactory messageHistoryFactory;
     private volatile StreamCachingStrategy streamCachingStrategy;
     private volatile InflightRepository inflightRepository;
+    private volatile ErrorRegistry errorRegistry;
     private volatile UuidGenerator uuidGenerator;
     private volatile Tracer tracer;
     private volatile TransformerRegistry transformerRegistry;
@@ -878,6 +880,24 @@ class DefaultCamelContextExtension implements 
ExtendedCamelContext {
         this.inflightRepository = 
camelContext.getInternalServiceManager().addService(camelContext, repository);
     }
 
+    ErrorRegistry getErrorRegistry() {
+        if (errorRegistry == null) {
+            lock.lock();
+            try {
+                if (errorRegistry == null) {
+                    setErrorRegistry(camelContext.createErrorRegistry());
+                }
+            } finally {
+                lock.unlock();
+            }
+        }
+        return errorRegistry;
+    }
+
+    void setErrorRegistry(ErrorRegistry errorRegistry) {
+        this.errorRegistry = 
camelContext.getInternalServiceManager().addService(camelContext, 
errorRegistry);
+    }
+
     UuidGenerator getUuidGenerator() {
         if (uuidGenerator == null) {
             lock.lock();
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
new file mode 100644
index 000000000000..2a8f006d60df
--- /dev/null
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultErrorRegistry.java
@@ -0,0 +1,289 @@
+/*
+ * 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.impl.engine;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentLinkedDeque;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.ExchangePropertyKey;
+import org.apache.camel.NonManagedService;
+import org.apache.camel.spi.CamelEvent;
+import org.apache.camel.spi.ErrorRegistry;
+import org.apache.camel.spi.ErrorRegistryEntry;
+import org.apache.camel.spi.ErrorRegistryView;
+import org.apache.camel.support.EventNotifierSupport;
+
+/**
+ * Default {@link ErrorRegistry} implementation that listens to exchange 
failure events and captures error snapshots.
+ */
+public class DefaultErrorRegistry extends EventNotifierSupport implements 
ErrorRegistry, NonManagedService {
+
+    private final ConcurrentLinkedDeque<ErrorRegistryEntry> entries = new 
ConcurrentLinkedDeque<>();
+    private volatile boolean enabled;
+    private volatile int maximumEntries = 100;
+    private volatile Duration timeToLive = Duration.ofHours(1);
+    private volatile boolean stackTraceEnabled;
+
+    public DefaultErrorRegistry() {
+        // only listen to exchange failure events
+        setIgnoreCamelContextEvents(true);
+        setIgnoreCamelContextInitEvents(true);
+        setIgnoreRouteEvents(true);
+        setIgnoreServiceEvents(true);
+        setIgnoreExchangeCreatedEvent(true);
+        setIgnoreExchangeCompletedEvent(true);
+        setIgnoreExchangeRedeliveryEvents(true);
+        setIgnoreExchangeSentEvents(true);
+        setIgnoreExchangeSendingEvents(true);
+        setIgnoreExchangeAsyncProcessingStartedEvents(true);
+        setIgnoreStepEvents(true);
+    }
+
+    @Override
+    public void notify(CamelEvent event) throws Exception {
+        if (!enabled) {
+            return;
+        }
+        if (event instanceof CamelEvent.ExchangeFailedEvent e) {
+            capture(e.getExchange(), false);
+        } else if (event instanceof CamelEvent.ExchangeFailureHandledEvent e) {
+            capture(e.getExchange(), true);
+        }
+    }
+
+    @Override
+    public boolean isEnabled(CamelEvent event) {
+        return enabled;
+    }
+
+    @Override
+    public boolean isDisabled() {
+        return !enabled;
+    }
+
+    private void capture(Exchange exchange, boolean handled) {
+        Throwable exception;
+        if (handled) {
+            // when handled, the exception has been moved to EXCEPTION_CAUGHT 
property
+            exception = 
exchange.getProperty(ExchangePropertyKey.EXCEPTION_CAUGHT, Throwable.class);
+        } else {
+            exception = exchange.getException();
+        }
+        if (exception == null) {
+            return;
+        }
+
+        String exchangeId = exchange.getExchangeId();
+        String routeId = 
exchange.getProperty(ExchangePropertyKey.FAILURE_ROUTE_ID, String.class);
+        if (routeId == null) {
+            routeId = exchange.getFromRouteId();
+        }
+        String endpointUri = 
exchange.getProperty(ExchangePropertyKey.FAILURE_ENDPOINT, String.class);
+        String exceptionType = exception.getClass().getName();
+        String exceptionMessage = exception.getMessage();
+        String[] stackTrace = stackTraceEnabled ? captureStackTrace(exception) 
: null;
+
+        DefaultErrorRegistryEntry entry = new DefaultErrorRegistryEntry(
+                exchangeId, routeId, endpointUri, Instant.now(),
+                handled, exceptionType, exceptionMessage, stackTrace);
+
+        entries.addFirst(entry);
+        evict();
+    }
+
+    private static String[] captureStackTrace(Throwable exception) {
+        StringWriter writer = new StringWriter();
+        exception.printStackTrace(new PrintWriter(writer, true));
+        return writer.toString().split("\n");
+    }
+
+    private void evict() {
+        // remove excess entries beyond maximum
+        while (entries.size() > maximumEntries) {
+            entries.pollLast();
+        }
+        // remove expired entries from the tail (oldest)
+        Instant cutoff = Instant.now().minus(timeToLive);
+        while (!entries.isEmpty()) {
+            ErrorRegistryEntry last = entries.peekLast();
+            if (last != null && last.timestamp().isBefore(cutoff)) {
+                entries.pollLast();
+            } else {
+                break;
+            }
+        }
+    }
+
+    // -- View methods (global scope) --
+
+    @Override
+    public int size() {
+        evict();
+        return entries.size();
+    }
+
+    @Override
+    public Collection<ErrorRegistryEntry> browse() {
+        return browse(-1);
+    }
+
+    @Override
+    public Collection<ErrorRegistryEntry> browse(int limit) {
+        evict();
+        if (limit <= 0) {
+            return Collections.unmodifiableList(new ArrayList<>(entries));
+        }
+        List<ErrorRegistryEntry> result = new ArrayList<>(Math.min(limit, 
entries.size()));
+        int count = 0;
+        for (ErrorRegistryEntry entry : entries) {
+            if (count >= limit) {
+                break;
+            }
+            result.add(entry);
+            count++;
+        }
+        return Collections.unmodifiableList(result);
+    }
+
+    @Override
+    public void clear() {
+        entries.clear();
+    }
+
+    // -- Scoped view --
+
+    @Override
+    public ErrorRegistryView forRoute(String routeId) {
+        return new RouteView(routeId);
+    }
+
+    // -- Configuration --
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @Override
+    public int getMaximumEntries() {
+        return maximumEntries;
+    }
+
+    @Override
+    public void setMaximumEntries(int maximumEntries) {
+        this.maximumEntries = maximumEntries;
+    }
+
+    @Override
+    public Duration getTimeToLive() {
+        return timeToLive;
+    }
+
+    @Override
+    public void setTimeToLive(Duration timeToLive) {
+        this.timeToLive = timeToLive;
+    }
+
+    @Override
+    public boolean isStackTraceEnabled() {
+        return stackTraceEnabled;
+    }
+
+    @Override
+    public void setStackTraceEnabled(boolean stackTraceEnabled) {
+        this.stackTraceEnabled = stackTraceEnabled;
+    }
+
+    /**
+     * A filtered view over entries for a specific route.
+     */
+    private class RouteView implements ErrorRegistryView {
+
+        private final String routeId;
+
+        RouteView(String routeId) {
+            this.routeId = Objects.requireNonNull(routeId);
+        }
+
+        @Override
+        public int size() {
+            evict();
+            int count = 0;
+            for (ErrorRegistryEntry entry : entries) {
+                if (routeId.equals(entry.routeId())) {
+                    count++;
+                }
+            }
+            return count;
+        }
+
+        @Override
+        public Collection<ErrorRegistryEntry> browse() {
+            return browse(-1);
+        }
+
+        @Override
+        public Collection<ErrorRegistryEntry> browse(int limit) {
+            evict();
+            List<ErrorRegistryEntry> result = new ArrayList<>();
+            for (ErrorRegistryEntry entry : entries) {
+                if (routeId.equals(entry.routeId())) {
+                    result.add(entry);
+                    if (limit > 0 && result.size() >= limit) {
+                        break;
+                    }
+                }
+            }
+            return Collections.unmodifiableList(result);
+        }
+
+        @Override
+        public void clear() {
+            entries.removeIf(entry -> routeId.equals(entry.routeId()));
+        }
+    }
+
+    /**
+     * Immutable snapshot of an error.
+     */
+    private record DefaultErrorRegistryEntry(
+            String exchangeId,
+            String routeId,
+            String endpointUri,
+            Instant timestamp,
+            boolean handled,
+            String exceptionType,
+            String exceptionMessage,
+            String[] stackTrace)
+            implements
+                ErrorRegistryEntry {
+    }
+}
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java
index 03af8dd61b8d..23db758d00d6 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java
@@ -52,6 +52,7 @@ import org.apache.camel.spi.DeferServiceFactory;
 import org.apache.camel.spi.DumpRoutesStrategy;
 import org.apache.camel.spi.EndpointRegistry;
 import org.apache.camel.spi.EndpointServiceRegistry;
+import org.apache.camel.spi.ErrorRegistry;
 import org.apache.camel.spi.ExchangeFactory;
 import org.apache.camel.spi.ExchangeFactoryManager;
 import org.apache.camel.spi.ExecutorServiceManager;
@@ -343,6 +344,11 @@ public class SimpleCamelContext extends 
AbstractCamelContext {
         return new DefaultInflightRepository();
     }
 
+    @Override
+    protected ErrorRegistry createErrorRegistry() {
+        return new DefaultErrorRegistry();
+    }
+
     @Override
     protected AsyncProcessorAwaitManager createAsyncProcessorAwaitManager() {
         return new DefaultAsyncProcessorAwaitManager();
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/errors.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/errors.json
new file mode 100644
index 000000000000..62a53fcd97bc
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/errors.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "errors",
+    "title": "Error Registry",
+    "description": "Display captured routing errors",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.ErrorRegistryConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-console",
+    "version": "4.19.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/errors
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/errors
new file mode 100644
index 000000000000..16f177d2d647
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/errors
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.console.ErrorRegistryConsole
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
index cfe26d07ff4b..a322d5a6b410 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
@@ -1,5 +1,5 @@
 # Generated by camel build tools - do NOT edit this file!
-dev-consoles=bean blocked browse circuit-breaker consumer context debug 
endpoint eval-language event gc health inflight internal-tasks java-security 
jvm log memory message-history processor producer properties receive reload 
rest route route-controller route-dump route-group route-structure send service 
simple-language source startup-recorder system-properties thread top trace 
transformers type-converters variables
+dev-consoles=bean blocked browse circuit-breaker consumer context debug 
endpoint errors eval-language event gc health inflight internal-tasks 
java-security jvm log memory message-history processor producer properties 
receive reload rest route route-controller route-dump route-group 
route-structure send service simple-language source startup-recorder 
system-properties thread top trace transformers type-converters variables
 groupId=org.apache.camel
 artifactId=camel-console
 version=4.19.0-SNAPSHOT
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
new file mode 100644
index 000000000000..7dd8229f071e
--- /dev/null
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ErrorRegistryConsole.java
@@ -0,0 +1,134 @@
+/*
+ * 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.impl.console;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.spi.ErrorRegistry;
+import org.apache.camel.spi.ErrorRegistryEntry;
+import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.console.AbstractDevConsole;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+@DevConsole(name = "errors", displayName = "Error Registry", description = 
"Display captured routing errors")
+public class ErrorRegistryConsole extends AbstractDevConsole {
+
+    /**
+     * Filter by route id
+     */
+    public static final String ROUTE_ID = "routeId";
+
+    /**
+     * Limits the number of entries displayed
+     */
+    public static final String LIMIT = "limit";
+
+    /**
+     * Whether to include stack traces
+     */
+    public static final String STACK_TRACE = "stackTrace";
+
+    public ErrorRegistryConsole() {
+        super("camel", "errors", "Error Registry", "Display captured routing 
errors");
+    }
+
+    @Override
+    protected String doCallText(Map<String, Object> options) {
+        String routeId = (String) options.get(ROUTE_ID);
+        String limit = (String) options.get(LIMIT);
+        int max = limit == null ? Integer.MAX_VALUE : Integer.parseInt(limit);
+        boolean includeStackTrace = "true".equals(options.get(STACK_TRACE));
+
+        StringBuilder sb = new StringBuilder();
+
+        ErrorRegistry registry = getCamelContext().getErrorRegistry();
+        sb.append(String.format("%n    Enabled: %s", registry.isEnabled()));
+        sb.append(String.format("%n    Size: %s", registry.size()));
+
+        Collection<ErrorRegistryEntry> entries;
+        if (routeId != null) {
+            entries = registry.forRoute(routeId).browse(max);
+        } else {
+            entries = registry.browse(max);
+        }
+
+        for (ErrorRegistryEntry entry : entries) {
+            sb.append(String.format("%n    %s (route: %s, endpoint: %s, 
handled: %s, type: %s, message: %s, timestamp: %s)",
+                    entry.exchangeId(), entry.routeId(), entry.endpointUri(),
+                    entry.handled(), entry.exceptionType(), 
entry.exceptionMessage(),
+                    entry.timestamp()));
+            if (includeStackTrace && entry.stackTrace() != null) {
+                for (String line : entry.stackTrace()) {
+                    sb.append(String.format("%n        %s", line));
+                }
+            }
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    protected JsonObject doCallJson(Map<String, Object> options) {
+        String routeId = (String) options.get(ROUTE_ID);
+        String limit = (String) options.get(LIMIT);
+        int max = limit == null ? Integer.MAX_VALUE : Integer.parseInt(limit);
+        boolean includeStackTrace = "true".equals(options.get(STACK_TRACE));
+
+        JsonObject root = new JsonObject();
+
+        ErrorRegistry registry = getCamelContext().getErrorRegistry();
+        root.put("enabled", registry.isEnabled());
+        root.put("size", registry.size());
+        root.put("maximumEntries", registry.getMaximumEntries());
+        root.put("timeToLive", registry.getTimeToLive().toString());
+        root.put("stackTraceEnabled", registry.isStackTraceEnabled());
+
+        Collection<ErrorRegistryEntry> entries;
+        if (routeId != null) {
+            entries = registry.forRoute(routeId).browse(max);
+        } else {
+            entries = registry.browse(max);
+        }
+
+        final List<JsonObject> list = new ArrayList<>();
+        for (ErrorRegistryEntry entry : entries) {
+            JsonObject jo = new JsonObject();
+            jo.put("exchangeId", entry.exchangeId());
+            jo.put("routeId", entry.routeId());
+            jo.put("endpointUri", entry.endpointUri());
+            jo.put("timestamp", entry.timestamp().toString());
+            jo.put("handled", entry.handled());
+            jo.put("exceptionType", entry.exceptionType());
+            jo.put("exceptionMessage", entry.exceptionMessage());
+            if (includeStackTrace && entry.stackTrace() != null) {
+                JsonArray stackTrace = new JsonArray();
+                for (String line : entry.stackTrace()) {
+                    stackTrace.add(line);
+                }
+                jo.put("stackTrace", stackTrace);
+            }
+            list.add(jo);
+        }
+        root.put("errors", list);
+
+        return root;
+    }
+}
diff --git 
a/core/camel-core-engine/src/generated/java/org/apache/camel/impl/CamelContextConfigurer.java
 
b/core/camel-core-engine/src/generated/java/org/apache/camel/impl/CamelContextConfigurer.java
index 50bb74825b3a..cceb0c83c9c2 100644
--- 
a/core/camel-core-engine/src/generated/java/org/apache/camel/impl/CamelContextConfigurer.java
+++ 
b/core/camel-core-engine/src/generated/java/org/apache/camel/impl/CamelContextConfigurer.java
@@ -54,6 +54,8 @@ public class CamelContextConfigurer extends 
org.apache.camel.support.component.P
         case "devConsole": target.setDevConsole(property(camelContext, 
java.lang.Boolean.class, value)); return true;
         case "dumproutes":
         case "dumpRoutes": target.setDumpRoutes(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "errorregistry":
+        case "errorRegistry": target.setErrorRegistry(property(camelContext, 
org.apache.camel.spi.ErrorRegistry.class, value)); return true;
         case "executorservicemanager":
         case "executorServiceManager": 
target.setExecutorServiceManager(property(camelContext, 
org.apache.camel.spi.ExecutorServiceManager.class, value)); return true;
         case "globaloptions":
@@ -174,6 +176,8 @@ public class CamelContextConfigurer extends 
org.apache.camel.support.component.P
         case "devConsole": return java.lang.Boolean.class;
         case "dumproutes":
         case "dumpRoutes": return java.lang.String.class;
+        case "errorregistry":
+        case "errorRegistry": return org.apache.camel.spi.ErrorRegistry.class;
         case "executorservicemanager":
         case "executorServiceManager": return 
org.apache.camel.spi.ExecutorServiceManager.class;
         case "globaloptions":
@@ -295,6 +299,8 @@ public class CamelContextConfigurer extends 
org.apache.camel.support.component.P
         case "devConsole": return target.isDevConsole();
         case "dumproutes":
         case "dumpRoutes": return target.getDumpRoutes();
+        case "errorregistry":
+        case "errorRegistry": return target.getErrorRegistry();
         case "executorservicemanager":
         case "executorServiceManager": return 
target.getExecutorServiceManager();
         case "globaloptions":
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
new file mode 100644
index 000000000000..f06ca81b5ef1
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/impl/ErrorRegistryTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.impl;
+
+import java.util.Collection;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.ErrorRegistry;
+import org.apache.camel.spi.ErrorRegistryEntry;
+import org.apache.camel.spi.ErrorRegistryView;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ErrorRegistryTest extends ContextTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getErrorRegistry().setEnabled(true);
+        return context;
+    }
+
+    @Test
+    public void testErrorRegistryCapturesHandledError() throws Exception {
+        getMockEndpoint("mock:dead").expectedMessageCount(1);
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+
+        ErrorRegistry registry = context.getErrorRegistry();
+        Collection<ErrorRegistryEntry> entries = registry.browse();
+        assertEquals(1, entries.size());
+
+        ErrorRegistryEntry entry = entries.iterator().next();
+        assertNotNull(entry.exchangeId());
+        assertEquals("foo", entry.routeId());
+        assertNotNull(entry.timestamp());
+        assertTrue(entry.handled());
+        assertEquals("java.lang.IllegalArgumentException", 
entry.exceptionType());
+        assertEquals("Forced error", entry.exceptionMessage());
+        assertNull(entry.stackTrace());
+    }
+
+    @Test
+    public void testErrorRegistryDisabled() throws Exception {
+        context.getErrorRegistry().setEnabled(false);
+
+        getMockEndpoint("mock:dead").expectedMessageCount(1);
+        template.sendBody("direct:start", "Hello World");
+        assertMockEndpointsSatisfied();
+
+        assertEquals(0, context.getErrorRegistry().size());
+    }
+
+    @Test
+    public void testErrorRegistryForRoute() throws Exception {
+        getMockEndpoint("mock:dead").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start2", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        ErrorRegistry registry = context.getErrorRegistry();
+        assertEquals(2, registry.size());
+
+        // test forRoute view
+        ErrorRegistryView fooView = registry.forRoute("foo");
+        assertEquals(1, fooView.size());
+        ErrorRegistryEntry fooEntry = fooView.browse().iterator().next();
+        assertEquals("foo", fooEntry.routeId());
+
+        ErrorRegistryView barView = registry.forRoute("bar");
+        assertEquals(1, barView.size());
+
+        // clear only foo route
+        fooView.clear();
+        assertEquals(1, registry.size());
+        assertEquals(0, fooView.size());
+        assertEquals(1, barView.size());
+    }
+
+    @Test
+    public void testErrorRegistryMaximumEntries() throws Exception {
+        context.getErrorRegistry().setMaximumEntries(2);
+
+        getMockEndpoint("mock:dead").expectedMessageCount(3);
+
+        template.sendBody("direct:start", "A");
+        template.sendBody("direct:start", "B");
+        template.sendBody("direct:start", "C");
+
+        assertMockEndpointsSatisfied();
+
+        // only 2 most recent entries should be kept
+        assertEquals(2, context.getErrorRegistry().size());
+    }
+
+    @Test
+    public void testErrorRegistryWithStackTrace() throws Exception {
+        context.getErrorRegistry().setStackTraceEnabled(true);
+
+        getMockEndpoint("mock:dead").expectedMessageCount(1);
+        template.sendBody("direct:start", "Hello World");
+        assertMockEndpointsSatisfied();
+
+        ErrorRegistryEntry entry = 
context.getErrorRegistry().browse().iterator().next();
+        assertNotNull(entry.stackTrace());
+        assertTrue(entry.stackTrace().length > 0);
+        assertTrue(entry.stackTrace()[0].contains("IllegalArgumentException"));
+    }
+
+    @Test
+    public void testErrorRegistryBrowseLimit() throws Exception {
+        getMockEndpoint("mock:dead").expectedMessageCount(3);
+
+        template.sendBody("direct:start", "A");
+        template.sendBody("direct:start", "B");
+        template.sendBody("direct:start", "C");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(3, context.getErrorRegistry().size());
+        assertEquals(2, context.getErrorRegistry().browse(2).size());
+    }
+
+    @Test
+    public void testErrorRegistryClear() throws Exception {
+        getMockEndpoint("mock:dead").expectedMessageCount(1);
+        template.sendBody("direct:start", "Hello World");
+        assertMockEndpointsSatisfied();
+
+        assertEquals(1, context.getErrorRegistry().size());
+        context.getErrorRegistry().clear();
+        assertEquals(0, context.getErrorRegistry().size());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                errorHandler(deadLetterChannel("mock:dead"));
+
+                from("direct:start").routeId("foo")
+                        .throwException(new IllegalArgumentException("Forced 
error"));
+
+                from("direct:start2").routeId("bar")
+                        .throwException(new IllegalArgumentException("Forced 
error 2"));
+            }
+        };
+    }
+}
diff --git 
a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
 
b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
index 5ee376adff5d..7bd63898f023 100644
--- 
a/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
+++ 
b/core/camel-main/src/generated/java/org/apache/camel/main/MainConfigurationPropertiesConfigurer.java
@@ -62,6 +62,10 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         map.put("EndpointBridgeErrorHandler", boolean.class);
         map.put("EndpointLazyStartProducer", boolean.class);
         map.put("EndpointRuntimeStatisticsEnabled", boolean.class);
+        map.put("ErrorRegistryEnabled", boolean.class);
+        map.put("ErrorRegistryMaximumEntries", int.class);
+        map.put("ErrorRegistryStackTraceEnabled", boolean.class);
+        map.put("ErrorRegistryTimeToLiveSeconds", int.class);
         map.put("ExchangeFactory", java.lang.String.class);
         map.put("ExchangeFactoryCapacity", int.class);
         map.put("ExchangeFactoryStatisticsEnabled", boolean.class);
@@ -233,6 +237,14 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "endpointLazyStartProducer": 
target.setEndpointLazyStartProducer(property(camelContext, boolean.class, 
value)); return true;
         case "endpointruntimestatisticsenabled":
         case "endpointRuntimeStatisticsEnabled": 
target.setEndpointRuntimeStatisticsEnabled(property(camelContext, 
boolean.class, value)); return true;
+        case "errorregistryenabled":
+        case "errorRegistryEnabled": 
target.setErrorRegistryEnabled(property(camelContext, boolean.class, value)); 
return true;
+        case "errorregistrymaximumentries":
+        case "errorRegistryMaximumEntries": 
target.setErrorRegistryMaximumEntries(property(camelContext, int.class, 
value)); return true;
+        case "errorregistrystacktraceenabled":
+        case "errorRegistryStackTraceEnabled": 
target.setErrorRegistryStackTraceEnabled(property(camelContext, boolean.class, 
value)); return true;
+        case "errorregistrytimetoliveseconds":
+        case "errorRegistryTimeToLiveSeconds": 
target.setErrorRegistryTimeToLiveSeconds(property(camelContext, int.class, 
value)); return true;
         case "exchangefactory":
         case "exchangeFactory": 
target.setExchangeFactory(property(camelContext, java.lang.String.class, 
value)); return true;
         case "exchangefactorycapacity":
@@ -491,6 +503,14 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "endpointLazyStartProducer": return boolean.class;
         case "endpointruntimestatisticsenabled":
         case "endpointRuntimeStatisticsEnabled": return boolean.class;
+        case "errorregistryenabled":
+        case "errorRegistryEnabled": return boolean.class;
+        case "errorregistrymaximumentries":
+        case "errorRegistryMaximumEntries": return int.class;
+        case "errorregistrystacktraceenabled":
+        case "errorRegistryStackTraceEnabled": return boolean.class;
+        case "errorregistrytimetoliveseconds":
+        case "errorRegistryTimeToLiveSeconds": return int.class;
         case "exchangefactory":
         case "exchangeFactory": return java.lang.String.class;
         case "exchangefactorycapacity":
@@ -745,6 +765,14 @@ public class MainConfigurationPropertiesConfigurer extends 
org.apache.camel.supp
         case "endpointLazyStartProducer": return 
target.isEndpointLazyStartProducer();
         case "endpointruntimestatisticsenabled":
         case "endpointRuntimeStatisticsEnabled": return 
target.isEndpointRuntimeStatisticsEnabled();
+        case "errorregistryenabled":
+        case "errorRegistryEnabled": return target.isErrorRegistryEnabled();
+        case "errorregistrymaximumentries":
+        case "errorRegistryMaximumEntries": return 
target.getErrorRegistryMaximumEntries();
+        case "errorregistrystacktraceenabled":
+        case "errorRegistryStackTraceEnabled": return 
target.isErrorRegistryStackTraceEnabled();
+        case "errorregistrytimetoliveseconds":
+        case "errorRegistryTimeToLiveSeconds": return 
target.getErrorRegistryTimeToLiveSeconds();
         case "exchangefactory":
         case "exchangeFactory": return target.getExchangeFactory();
         case "exchangefactorycapacity":
diff --git 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index e0e8c6b15390..2656901d4cb7 100644
--- 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -69,6 +69,10 @@
     { "name": "camel.main.endpointBridgeErrorHandler", "required": false, 
"description": "Allows for bridging the consumer to the Camel routing Error 
Handler, which mean any exceptions occurred while the consumer is trying to 
pickup incoming messages, or the likes, will now be processed as a message and 
handled by the routing Error Handler. By default the consumer will use the 
org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be 
logged at WARN\/ERROR level and igno [...]
     { "name": "camel.main.endpointLazyStartProducer", "required": false, 
"description": "Whether the producer should be started lazy (on the first 
message). By starting lazy you can use this to allow CamelContext and routes to 
startup in situations where a producer may otherwise fail during starting and 
cause the route to fail being started. By deferring this startup to be lazy 
then the startup failure can be handled during routing messages via Camel's 
routing error handlers. Beware that [...]
     { "name": "camel.main.endpointRuntimeStatisticsEnabled", "required": 
false, "description": "Sets whether endpoint runtime statistics is enabled 
(gathers runtime usage of each incoming and outgoing endpoints). The default 
value is false.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
+    { "name": "camel.main.errorRegistryEnabled", "required": false, 
"description": "Sets whether the error registry is enabled to capture errors 
during message routing. This is by default disabled.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
+    { "name": "camel.main.errorRegistryMaximumEntries", "required": false, 
"description": "Sets the maximum number of error entries to keep in the error 
registry. When the limit is exceeded, the oldest entries are evicted. The 
default value is 100.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 100, "secret": false },
+    { "name": "camel.main.errorRegistryStackTraceEnabled", "required": false, 
"description": "Sets whether to capture stack traces in the error registry. 
This is disabled by default to reduce memory usage.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
+    { "name": "camel.main.errorRegistryTimeToLiveSeconds", "required": false, 
"description": "Sets the time-to-live in seconds for error entries in the error 
registry. Entries older than this are evicted. The default value is 3600 (1 
hour).", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", 
"type": "integer", "javaType": "int", "defaultValue": 3600, "secret": false },
     { "name": "camel.main.exchangeFactory", "required": false, "description": 
"Controls whether to pool (reuse) exchanges or create new exchanges 
(prototype). Using pooled will reduce JVM garbage collection overhead by 
avoiding to re-create Exchange instances per message each consumer receives. 
The default is prototype mode.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "enum", 
"javaType": "java.lang.String", "defaultValue": "default", "secret": false, " 
[...]
     { "name": "camel.main.exchangeFactoryCapacity", "required": false, 
"description": "The capacity the pool (for each consumer) uses for storing 
exchanges. The default capacity is 100.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 100, "secret": false },
     { "name": "camel.main.exchangeFactoryStatisticsEnabled", "required": 
false, "description": "Configures whether statistics is enabled on exchange 
factory.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
diff --git a/core/camel-main/src/main/docs/main.adoc 
b/core/camel-main/src/main/docs/main.adoc
index 179bc82bb8e3..4484d57b1015 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -19,7 +19,7 @@ The following tables lists all the options:
 
 // main options: START
 === Camel Main configurations
-The camel.main supports 126 options, which are listed below.
+The camel.main supports 130 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -64,6 +64,10 @@ The camel.main supports 126 options, which are listed below.
 | *camel.main.endpointBridgeError{zwsp}Handler* | Allows for bridging the 
consumer to the Camel routing Error Handler, which mean any exceptions occurred 
while the consumer is trying to pickup incoming messages, or the likes, will 
now be processed as a message and handled by the routing Error Handler. By 
default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal 
with exceptions, that will be logged at WARN/ERROR level and ignored. The 
default value is false. | false  [...]
 | *camel.main.endpointLazyStart{zwsp}Producer* | Whether the producer should 
be started lazy (on the first message). By starting lazy you can use this to 
allow CamelContext and routes to startup in situations where a producer may 
otherwise fail during starting and cause the route to fail being started. By 
deferring this startup to be lazy then the startup failure can be handled 
during routing messages via Camel's routing error handlers. Beware that when 
the first message is processed the [...]
 | *camel.main.endpointRuntime{zwsp}StatisticsEnabled* | Sets whether endpoint 
runtime statistics is enabled (gathers runtime usage of each incoming and 
outgoing endpoints). The default value is false. | false | boolean
+| *camel.main.errorRegistry{zwsp}Enabled* | Sets whether the error registry is 
enabled to capture errors during message routing. This is by default disabled. 
| false | boolean
+| *camel.main.errorRegistry{zwsp}MaximumEntries* | Sets the maximum number of 
error entries to keep in the error registry. When the limit is exceeded, the 
oldest entries are evicted. The default value is 100. | 100 | int
+| *camel.main.errorRegistryStack{zwsp}TraceEnabled* | Sets whether to capture 
stack traces in the error registry. This is disabled by default to reduce 
memory usage. | false | boolean
+| *camel.main.errorRegistryTimeTo{zwsp}LiveSeconds* | Sets the time-to-live in 
seconds for error entries in the error registry. Entries older than this are 
evicted. The default value is 3600 (1 hour). | 3600 | int
 | *camel.main.exchangeFactory* | Controls whether to pool (reuse) exchanges or 
create new exchanges (prototype). Using pooled will reduce JVM garbage 
collection overhead by avoiding to re-create Exchange instances per message 
each consumer receives. The default is prototype mode. | default | String
 | *camel.main.exchangeFactory{zwsp}Capacity* | The capacity the pool (for each 
consumer) uses for storing exchanges. The default capacity is 100. | 100 | int
 | *camel.main.exchangeFactory{zwsp}StatisticsEnabled* | Configures whether 
statistics is enabled on exchange factory. | false | boolean
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
index 7aed2befc757..578c0a507a70 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationConfigurer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.main;
 
+import java.time.Duration;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -201,6 +202,11 @@ public final class DefaultConfigurationConfigurer {
 
         
camelContext.getInflightRepository().setInflightBrowseEnabled(config.isInflightRepositoryBrowseEnabled());
 
+        
camelContext.getErrorRegistry().setEnabled(config.isErrorRegistryEnabled());
+        
camelContext.getErrorRegistry().setMaximumEntries(config.getErrorRegistryMaximumEntries());
+        
camelContext.getErrorRegistry().setTimeToLive(Duration.ofSeconds(config.getErrorRegistryTimeToLiveSeconds()));
+        
camelContext.getErrorRegistry().setStackTraceEnabled(config.isErrorRegistryStackTraceEnabled());
+
         if (config.getLogDebugMaxChars() != 0) {
             
camelContext.getGlobalOptions().put(Exchange.LOG_DEBUG_BODY_MAX_CHARS,
                     Integer.toString(config.getLogDebugMaxChars()));
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index ff8a5923b22c..ed3c25bede42 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -47,6 +47,12 @@ public abstract class DefaultConfigurationProperties<T> {
     private boolean shutdownRoutesInReverseOrder = true;
     private boolean shutdownLogInflightExchangesOnTimeout = true;
     private boolean inflightRepositoryBrowseEnabled;
+    private boolean errorRegistryEnabled;
+    @Metadata(defaultValue = "100")
+    private int errorRegistryMaximumEntries = 100;
+    @Metadata(defaultValue = "3600")
+    private int errorRegistryTimeToLiveSeconds = 3600;
+    private boolean errorRegistryStackTraceEnabled;
     private String fileConfigurations;
     private boolean jmxEnabled = true;
     @Metadata(enums = "classic,default,short,simple,off", defaultValue = 
"default")
@@ -321,6 +327,57 @@ public abstract class DefaultConfigurationProperties<T> {
         this.inflightRepositoryBrowseEnabled = inflightRepositoryBrowseEnabled;
     }
 
+    public boolean isErrorRegistryEnabled() {
+        return errorRegistryEnabled;
+    }
+
+    /**
+     * Sets whether the error registry is enabled to capture errors during 
message routing.
+     *
+     * This is by default disabled.
+     */
+    public void setErrorRegistryEnabled(boolean errorRegistryEnabled) {
+        this.errorRegistryEnabled = errorRegistryEnabled;
+    }
+
+    public int getErrorRegistryMaximumEntries() {
+        return errorRegistryMaximumEntries;
+    }
+
+    /**
+     * Sets the maximum number of error entries to keep in the error registry. 
When the limit is exceeded, the oldest
+     * entries are evicted.
+     *
+     * The default value is 100.
+     */
+    public void setErrorRegistryMaximumEntries(int 
errorRegistryMaximumEntries) {
+        this.errorRegistryMaximumEntries = errorRegistryMaximumEntries;
+    }
+
+    public int getErrorRegistryTimeToLiveSeconds() {
+        return errorRegistryTimeToLiveSeconds;
+    }
+
+    /**
+     * Sets the time-to-live in seconds for error entries in the error 
registry. Entries older than this are evicted.
+     *
+     * The default value is 3600 (1 hour).
+     */
+    public void setErrorRegistryTimeToLiveSeconds(int 
errorRegistryTimeToLiveSeconds) {
+        this.errorRegistryTimeToLiveSeconds = errorRegistryTimeToLiveSeconds;
+    }
+
+    public boolean isErrorRegistryStackTraceEnabled() {
+        return errorRegistryStackTraceEnabled;
+    }
+
+    /**
+     * Sets whether to capture stack traces in the error registry. This is 
disabled by default to reduce memory usage.
+     */
+    public void setErrorRegistryStackTraceEnabled(boolean 
errorRegistryStackTraceEnabled) {
+        this.errorRegistryStackTraceEnabled = errorRegistryStackTraceEnabled;
+    }
+
     public String getFileConfigurations() {
         return fileConfigurations;
     }
@@ -1824,6 +1881,44 @@ public abstract class DefaultConfigurationProperties<T> {
         return (T) this;
     }
 
+    /**
+     * Sets whether the error registry is enabled to capture errors during 
message routing.
+     *
+     * This is by default disabled.
+     */
+    public T withErrorRegistryEnabled(boolean errorRegistryEnabled) {
+        this.errorRegistryEnabled = errorRegistryEnabled;
+        return (T) this;
+    }
+
+    /**
+     * Sets the maximum number of error entries to keep in the error registry.
+     *
+     * The default value is 100.
+     */
+    public T withErrorRegistryMaximumEntries(int errorRegistryMaximumEntries) {
+        this.errorRegistryMaximumEntries = errorRegistryMaximumEntries;
+        return (T) this;
+    }
+
+    /**
+     * Sets the time-to-live in seconds for error entries in the error 
registry.
+     *
+     * The default value is 3600 (1 hour).
+     */
+    public T withErrorRegistryTimeToLiveSeconds(int 
errorRegistryTimeToLiveSeconds) {
+        this.errorRegistryTimeToLiveSeconds = errorRegistryTimeToLiveSeconds;
+        return (T) this;
+    }
+
+    /**
+     * Sets whether to capture stack traces in the error registry.
+     */
+    public T withErrorRegistryStackTraceEnabled(boolean 
errorRegistryStackTraceEnabled) {
+        this.errorRegistryStackTraceEnabled = errorRegistryStackTraceEnabled;
+        return (T) this;
+    }
+
     /**
      * Directory to load additional configuration files that contains 
configuration values that takes precedence over
      * any other configuration. This can be used to refer to files that may 
have secret configuration that has been
diff --git 
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java
 
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java
index 48e83e07b00a..00445f80aa20 100644
--- 
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java
+++ 
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java
@@ -369,4 +369,23 @@ public final class CamelOpenMBeanTypes {
                         SimpleType.LONG, SimpleType.LONG, SimpleType.LONG, 
SimpleType.LONG, SimpleType.STRING });
     }
 
+    public static TabularType listErrorRegistryTabularType() throws 
OpenDataException {
+        CompositeType ct = listErrorRegistryCompositeType();
+        return new TabularType("listErrors", "Lists captured routing errors", 
ct, new String[] { "exchangeId" });
+    }
+
+    public static CompositeType listErrorRegistryCompositeType() throws 
OpenDataException {
+        return new CompositeType(
+                "errors", "Errors",
+                new String[] {
+                        "exchangeId", "routeId", "endpointUri", "timestamp",
+                        "handled", "exceptionType", "exceptionMessage" },
+                new String[] {
+                        "Exchange Id", "Route Id", "Endpoint Uri", "Timestamp",
+                        "Handled", "Exception Type", "Exception Message" },
+                new OpenType[] {
+                        SimpleType.STRING, SimpleType.STRING, 
SimpleType.STRING, SimpleType.STRING,
+                        SimpleType.BOOLEAN, SimpleType.STRING, 
SimpleType.STRING });
+    }
+
 }
diff --git 
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedErrorRegistryMBean.java
 
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedErrorRegistryMBean.java
new file mode 100644
index 000000000000..a88f1ad6848f
--- /dev/null
+++ 
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedErrorRegistryMBean.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api.management.mbean;
+
+import javax.management.openmbean.TabularData;
+
+import org.apache.camel.api.management.ManagedAttribute;
+import org.apache.camel.api.management.ManagedOperation;
+
+public interface ManagedErrorRegistryMBean extends ManagedServiceMBean {
+
+    @ManagedAttribute(description = "Whether the error registry is enabled")
+    boolean isEnabled();
+
+    @ManagedAttribute(description = "Whether the error registry is enabled")
+    void setEnabled(boolean enabled);
+
+    @ManagedAttribute(description = "Current number of error entries in the 
registry")
+    int getSize();
+
+    @ManagedAttribute(description = "Maximum number of error entries to keep")
+    int getMaximumEntries();
+
+    @ManagedAttribute(description = "Maximum number of error entries to keep")
+    void setMaximumEntries(int maximumEntries);
+
+    @ManagedAttribute(description = "Time-to-live in seconds for error 
entries")
+    long getTimeToLiveSeconds();
+
+    @ManagedAttribute(description = "Time-to-live in seconds for error 
entries")
+    void setTimeToLiveSeconds(long seconds);
+
+    @ManagedAttribute(description = "Whether stack trace capture is enabled")
+    boolean isStackTraceEnabled();
+
+    @ManagedAttribute(description = "Whether stack trace capture is enabled")
+    void setStackTraceEnabled(boolean stackTraceEnabled);
+
+    @ManagedOperation(description = "Browse all error entries")
+    TabularData browse();
+
+    @ManagedOperation(description = "Browse error entries with a limit")
+    TabularData browse(int limit);
+
+    @ManagedOperation(description = "Browse error entries for a specific 
route")
+    TabularData browse(String routeId, int limit);
+
+    @ManagedOperation(description = "Clear all error entries")
+    void clear();
+}
diff --git 
a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java
 
b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java
index b3629136e178..be5a14327610 100644
--- 
a/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java
+++ 
b/core/camel-management/src/main/java/org/apache/camel/management/JmxManagementLifecycleStrategy.java
@@ -63,6 +63,7 @@ import 
org.apache.camel.management.mbean.ManagedDumpRouteStrategy;
 import org.apache.camel.management.mbean.ManagedEndpoint;
 import org.apache.camel.management.mbean.ManagedEndpointRegistry;
 import org.apache.camel.management.mbean.ManagedEndpointServiceRegistry;
+import org.apache.camel.management.mbean.ManagedErrorRegistry;
 import org.apache.camel.management.mbean.ManagedExchangeFactoryManager;
 import org.apache.camel.management.mbean.ManagedInflightRepository;
 import org.apache.camel.management.mbean.ManagedProducerCache;
@@ -97,6 +98,7 @@ import org.apache.camel.spi.DataFormat;
 import org.apache.camel.spi.DumpRoutesStrategy;
 import org.apache.camel.spi.EndpointRegistry;
 import org.apache.camel.spi.EndpointServiceRegistry;
+import org.apache.camel.spi.ErrorRegistry;
 import org.apache.camel.spi.EventNotifier;
 import org.apache.camel.spi.ExchangeFactoryManager;
 import org.apache.camel.spi.InflightRepository;
@@ -583,6 +585,8 @@ public class JmxManagementLifecycleStrategy extends 
ServiceSupport implements Li
             answer = new ManagedEndpointServiceRegistry(context, 
endpointServiceRegistry);
         } else if (service instanceof InflightRepository inflightRepository) {
             answer = new ManagedInflightRepository(context, 
inflightRepository);
+        } else if (service instanceof ErrorRegistry errorRegistry) {
+            answer = new ManagedErrorRegistry(context, errorRegistry);
         } else if (service instanceof AsyncProcessorAwaitManager 
asyncProcessorAwaitManager) {
             answer = new ManagedAsyncProcessorAwaitManager(context, 
asyncProcessorAwaitManager);
         } else if (service instanceof RuntimeEndpointRegistry 
runtimeEndpointRegistry) {
diff --git 
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedErrorRegistry.java
 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedErrorRegistry.java
new file mode 100644
index 000000000000..eab3bcccf54f
--- /dev/null
+++ 
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedErrorRegistry.java
@@ -0,0 +1,140 @@
+/*
+ * 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.management.mbean;
+
+import java.time.Duration;
+import java.util.Collection;
+
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.api.management.ManagedResource;
+import org.apache.camel.api.management.mbean.CamelOpenMBeanTypes;
+import org.apache.camel.api.management.mbean.ManagedErrorRegistryMBean;
+import org.apache.camel.spi.ErrorRegistry;
+import org.apache.camel.spi.ErrorRegistryEntry;
+
+@ManagedResource(description = "Managed ErrorRegistry")
+public class ManagedErrorRegistry extends ManagedService implements 
ManagedErrorRegistryMBean {
+
+    private final ErrorRegistry errorRegistry;
+
+    public ManagedErrorRegistry(CamelContext context, ErrorRegistry 
errorRegistry) {
+        super(context, errorRegistry);
+        this.errorRegistry = errorRegistry;
+    }
+
+    public ErrorRegistry getErrorRegistry() {
+        return errorRegistry;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return errorRegistry.isEnabled();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        errorRegistry.setEnabled(enabled);
+    }
+
+    @Override
+    public int getSize() {
+        return errorRegistry.size();
+    }
+
+    @Override
+    public int getMaximumEntries() {
+        return errorRegistry.getMaximumEntries();
+    }
+
+    @Override
+    public void setMaximumEntries(int maximumEntries) {
+        errorRegistry.setMaximumEntries(maximumEntries);
+    }
+
+    @Override
+    public long getTimeToLiveSeconds() {
+        return errorRegistry.getTimeToLive().toSeconds();
+    }
+
+    @Override
+    public void setTimeToLiveSeconds(long seconds) {
+        errorRegistry.setTimeToLive(Duration.ofSeconds(seconds));
+    }
+
+    @Override
+    public boolean isStackTraceEnabled() {
+        return errorRegistry.isStackTraceEnabled();
+    }
+
+    @Override
+    public void setStackTraceEnabled(boolean stackTraceEnabled) {
+        errorRegistry.setStackTraceEnabled(stackTraceEnabled);
+    }
+
+    @Override
+    public TabularData browse() {
+        return browseEntries(errorRegistry.browse());
+    }
+
+    @Override
+    public TabularData browse(int limit) {
+        return browseEntries(errorRegistry.browse(limit));
+    }
+
+    @Override
+    public TabularData browse(String routeId, int limit) {
+        return browseEntries(errorRegistry.forRoute(routeId).browse(limit));
+    }
+
+    @Override
+    public void clear() {
+        errorRegistry.clear();
+    }
+
+    private static TabularData browseEntries(Collection<ErrorRegistryEntry> 
entries) {
+        try {
+            TabularData answer = new 
TabularDataSupport(CamelOpenMBeanTypes.listErrorRegistryTabularType());
+            for (ErrorRegistryEntry entry : entries) {
+                CompositeType ct = 
CamelOpenMBeanTypes.listErrorRegistryCompositeType();
+                CompositeData data = new CompositeDataSupport(
+                        ct,
+                        new String[] {
+                                "exchangeId", "routeId", "endpointUri", 
"timestamp",
+                                "handled", "exceptionType", "exceptionMessage" 
},
+                        new Object[] {
+                                entry.exchangeId(),
+                                entry.routeId(),
+                                entry.endpointUri(),
+                                entry.timestamp().toString(),
+                                entry.handled(),
+                                entry.exceptionType(),
+                                entry.exceptionMessage() });
+                answer.put(data);
+            }
+            return answer;
+        } catch (Exception e) {
+            throw RuntimeCamelException.wrapRuntimeCamelException(e);
+        }
+    }
+}

Reply via email to