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

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


The following commit(s) were added to refs/heads/main by this push:
     new d0abe420863f CAMEL-24819: count fallback, timed out and bulkhead 
rejected circuit breaker calls (#26603)
d0abe420863f is described below

commit d0abe420863f2b9ce5b4b33ccec9143c209c4d14
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Sep 18 22:10:21 2026 +0200

    CAMEL-24819: count fallback, timed out and bulkhead rejected circuit 
breaker calls (#26603)
    
    * CAMEL-24819: count fallback, timed out and bulkhead rejected circuit 
breaker calls
    
    ResilienceProcessor and FaultToleranceProcessor keep three counters,
    incremented on the code paths that set the CamelCircuitBreakerResponse*
    properties and reset by transitionToCloseState: calls answered by the
    onFallback, calls that timed out (which the breaker otherwise counts as
    plain failures) and calls rejected by a full bulkhead (which the not
    permitted metric does not include). They are exposed as managed
    attributes, in both dev consoles (JSON and text), as FALLBACK, TIMEOUT
    and, when a bulkhead is configured, BULKHEAD columns in camel get
    circuit-breaker (which now also shows the fault tolerance counts it
    skipped before), and in the TUI circuit breaker tab, its detail panel
    and help page.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
    
    * CAMEL-24819: TUI help page explains how to read the circuit breaker 
counters
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
    
    * Regen
    
    * CAMEL-24819: Address review feedback
    
    Test the bulkhead-with-fallback counter path in both processors: the
    first call holds the only permit while it is slow, the second is
    rejected by the bulkhead and answered by the fallback, and the rejected
    call is counted from inside the fallback path.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
    
    * CAMEL-24819: Address review feedback
    
    Synchronise the bulkhead tests on a latch instead of a sleep, and use
    assertFalse in the CLI test.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
    
    * CAMEL-24819: Address review feedback
    
    Test the timeout and bulkhead rejection counters on the paths without a
    fallback as well, in both processors.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
    
    * CAMEL-24819: Address review feedback
    
    The fault tolerance bulkhead tests assert that the second call was
    rejected with a BulkheadException, not queued; SmallRye needs a waiting
    queue of at least one, so the queue stays at one.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
    
    ---------
    
    Co-authored-by: Claude Fable 5.1 <[email protected]>
    Co-authored-by: Guillaume Nodet <[email protected]>
---
 .../apache/camel/catalog/dev-consoles-openapi.json |  32 +++-
 .../catalog/dev-consoles/fault-tolerance.json      |  17 +-
 .../camel/catalog/dev-consoles/resilience4j.json   |  15 ++
 .../apache/camel/dev-console/fault-tolerance.json  |  17 +-
 .../faulttolerance/FaultToleranceConsole.java      |  14 +-
 .../faulttolerance/FaultToleranceProcessor.java    |  36 +++++
 .../FaultToleranceCallCountersTest.java            | 180 +++++++++++++++++++++
 .../faulttolerance/FaultToleranceConsoleTest.java  |   5 +
 .../org/apache/camel/dev-console/resilience4j.json |  15 ++
 .../component/resilience4j/ResilienceConsole.java  |  17 +-
 .../resilience4j/ResilienceProcessor.java          |  32 +++-
 .../resilience4j/ResilienceCallCountersTest.java   | 172 ++++++++++++++++++++
 .../resilience4j/ResilienceConsoleTest.java        |   5 +
 .../modules/ROOT/pages/camel-jbang-managing.adoc   |   9 +-
 .../core/commands/process/ListCircuitBreaker.java  |  87 +++++++---
 .../commands/process/ListCircuitBreakerTest.java   |  51 ++++++
 .../core/commands/tui/CircuitBreakerInfo.java      |   3 +
 .../jbang/core/commands/tui/CircuitBreakerTab.java |  16 +-
 .../dsl/jbang/core/commands/tui/StatusParser.java  |   3 +
 .../src/main/resources/tui/help/circuit-breaker.md |  40 ++++-
 .../commands/tui/CircuitBreakerTabRenderTest.java  |  16 ++
 21 files changed, 741 insertions(+), 41 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
index b980d31ba932..55b44b133d31 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
@@ -1976,6 +1976,18 @@
                                                                                
                                "type": "integer",
                                                                                
                                "description": "Number of not-permitted calls"
                                                                                
                        },
+                                                                               
                        "fallbackCalls": {
+                                                                               
                                "type": "integer",
+                                                                               
                                "description": "Number of calls answered by the 
onFallback"
+                                                                               
                        },
+                                                                               
                        "timedOutCalls": {
+                                                                               
                                "type": "integer",
+                                                                               
                                "description": "Number of calls that timed out"
+                                                                               
                        },
+                                                                               
                        "bulkheadRejectedCalls": {
+                                                                               
                                "type": "integer",
+                                                                               
                                "description": "Number of calls rejected 
because the bulkhead was full"
+                                                                               
                        },
                                                                                
                        "configuration": {
                                                                                
                                "type": "object",
                                                                                
                                "properties": {
@@ -2030,7 +2042,10 @@
                                                                                
                "required": [
                                                                                
                        "successfulCalls",
                                                                                
                        "failedCalls",
-                                                                               
                        "notPermittedCalls"
+                                                                               
                        "notPermittedCalls",
+                                                                               
                        "fallbackCalls",
+                                                                               
                        "timedOutCalls",
+                                                                               
                        "bulkheadRejectedCalls"
                                                                                
                ]
                                                                                
        },
                                                                                
        "description": "The circuit breakers"
@@ -5434,6 +5449,18 @@
                                                                                
                                "type": "integer",
                                                                                
                                "description": "The number of not permitted 
calls"
                                                                                
                        },
+                                                                               
                        "fallbackCalls": {
+                                                                               
                                "type": "integer",
+                                                                               
                                "description": "The number of calls answered by 
the onFallback"
+                                                                               
                        },
+                                                                               
                        "timedOutCalls": {
+                                                                               
                                "type": "integer",
+                                                                               
                                "description": "The number of calls that timed 
out"
+                                                                               
                        },
+                                                                               
                        "bulkheadRejectedCalls": {
+                                                                               
                                "type": "integer",
+                                                                               
                                "description": "The number of calls rejected 
because the bulkhead was full"
+                                                                               
                        },
                                                                                
                        "failureRate": {
                                                                                
                                "type": "number",
                                                                                
                                "description": "The failure rate in percentage"
@@ -5513,6 +5540,9 @@
                                                                                
                        "successfulCalls",
                                                                                
                        "failedCalls",
                                                                                
                        "notPermittedCalls",
+                                                                               
                        "fallbackCalls",
+                                                                               
                        "timedOutCalls",
+                                                                               
                        "bulkheadRejectedCalls",
                                                                                
                        "failureRate"
                                                                                
                ]
                                                                                
        },
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/fault-tolerance.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/fault-tolerance.json
index 0da68dd32845..75d56e6744f9 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/fault-tolerance.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/fault-tolerance.json
@@ -44,6 +44,18 @@
               "type": "integer",
               "description": "Number of not-permitted calls"
             },
+            "fallbackCalls": {
+              "type": "integer",
+              "description": "Number of calls answered by the onFallback"
+            },
+            "timedOutCalls": {
+              "type": "integer",
+              "description": "Number of calls that timed out"
+            },
+            "bulkheadRejectedCalls": {
+              "type": "integer",
+              "description": "Number of calls rejected because the bulkhead 
was full"
+            },
             "configuration": {
               "type": "object",
               "properties": {
@@ -98,7 +110,10 @@
           "required": [
             "successfulCalls",
             "failedCalls",
-            "notPermittedCalls"
+            "notPermittedCalls",
+            "fallbackCalls",
+            "timedOutCalls",
+            "bulkheadRejectedCalls"
           ]
         },
         "description": "The circuit breakers"
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/resilience4j.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/resilience4j.json
index d1553e7e2fa5..210626297a5a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/resilience4j.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/resilience4j.json
@@ -48,6 +48,18 @@
               "type": "integer",
               "description": "The number of not permitted calls"
             },
+            "fallbackCalls": {
+              "type": "integer",
+              "description": "The number of calls answered by the onFallback"
+            },
+            "timedOutCalls": {
+              "type": "integer",
+              "description": "The number of calls that timed out"
+            },
+            "bulkheadRejectedCalls": {
+              "type": "integer",
+              "description": "The number of calls rejected because the 
bulkhead was full"
+            },
             "failureRate": {
               "type": "number",
               "description": "The failure rate in percentage"
@@ -127,6 +139,9 @@
             "successfulCalls",
             "failedCalls",
             "notPermittedCalls",
+            "fallbackCalls",
+            "timedOutCalls",
+            "bulkheadRejectedCalls",
             "failureRate"
           ]
         },
diff --git 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/generated/resources/META-INF/org/apache/camel/dev-console/fault-tolerance.json
 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/generated/resources/META-INF/org/apache/camel/dev-console/fault-tolerance.json
index 0da68dd32845..75d56e6744f9 100644
--- 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/generated/resources/META-INF/org/apache/camel/dev-console/fault-tolerance.json
+++ 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/generated/resources/META-INF/org/apache/camel/dev-console/fault-tolerance.json
@@ -44,6 +44,18 @@
               "type": "integer",
               "description": "Number of not-permitted calls"
             },
+            "fallbackCalls": {
+              "type": "integer",
+              "description": "Number of calls answered by the onFallback"
+            },
+            "timedOutCalls": {
+              "type": "integer",
+              "description": "Number of calls that timed out"
+            },
+            "bulkheadRejectedCalls": {
+              "type": "integer",
+              "description": "Number of calls rejected because the bulkhead 
was full"
+            },
             "configuration": {
               "type": "object",
               "properties": {
@@ -98,7 +110,10 @@
           "required": [
             "successfulCalls",
             "failedCalls",
-            "notPermittedCalls"
+            "notPermittedCalls",
+            "fallbackCalls",
+            "timedOutCalls",
+            "bulkheadRejectedCalls"
           ]
         },
         "description": "The circuit breakers"
diff --git 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsole.java
 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsole.java
index 8e923facd1bc..567aa9907d83 100644
--- 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsole.java
+++ 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsole.java
@@ -51,6 +51,9 @@ public class FaultToleranceConsole extends AbstractDevConsole 
{
             @Metadata(description = "Number of successful calls") long 
successfulCalls,
             @Metadata(description = "Number of failed calls") long failedCalls,
             @Metadata(description = "Number of not-permitted calls") long 
notPermittedCalls,
+            @Metadata(description = "Number of calls answered by the 
onFallback") long fallbackCalls,
+            @Metadata(description = "Number of calls that timed out") long 
timedOutCalls,
+            @Metadata(description = "Number of calls rejected because the 
bulkhead was full") long bulkheadRejectedCalls,
             @Metadata(description = "The circuit breaker configuration") 
Configuration configuration) {
     }
 
@@ -85,8 +88,12 @@ public class FaultToleranceConsole extends 
AbstractDevConsole {
             long sc = cb.getNumberOfSuccessfulCalls();
             long fc = cb.getNumberOfFailedCalls();
             long npc = cb.getNumberOfNotPermittedCalls();
-            sb.append(String.format("    %s/%s: %s (success: %d failure: %d 
not-permitted: %d)%n",
-                    rid, id, state, sc, fc, npc));
+            long fb = cb.getNumberOfFallbackCalls();
+            long to = cb.getNumberOfTimedOutCalls();
+            long br = cb.getNumberOfBulkheadRejectedCalls();
+            sb.append(String.format(
+                    "    %s/%s: %s (success: %d failure: %d not-permitted: %d 
fallback: %d timed-out: %d bulkhead-rejected: %d)%n",
+                    rid, id, state, sc, fc, npc, fb, to, br));
         }
 
         return sb.toString();
@@ -123,7 +130,8 @@ public class FaultToleranceConsole extends 
AbstractDevConsole {
 
             list.add(new CircuitBreakerEntry(
                     cb.getId(), cb.getRouteId(), cb.getCircuitBreakerState(), 
cb.getNumberOfSuccessfulCalls(),
-                    cb.getNumberOfFailedCalls(), 
cb.getNumberOfNotPermittedCalls(), config));
+                    cb.getNumberOfFailedCalls(), 
cb.getNumberOfNotPermittedCalls(), cb.getNumberOfFallbackCalls(),
+                    cb.getNumberOfTimedOutCalls(), 
cb.getNumberOfBulkheadRejectedCalls(), config));
         }
 
         Response response = new Response(list);
diff --git 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java
 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java
index a3c3b0b86a1b..31fb84bc7213 100644
--- 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java
+++ 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java
@@ -86,6 +86,10 @@ public class FaultToleranceProcessor extends 
BaseProcessorSupport
     private final AtomicLong successfulCalls = new AtomicLong();
     private final AtomicLong failedCalls = new AtomicLong();
     private final AtomicLong notPermittedCalls = new AtomicLong();
+    // counters for what the circuit breaker listeners do not tell apart
+    private final AtomicLong fallbackCalls = new AtomicLong();
+    private final AtomicLong timedOutCalls = new AtomicLong();
+    private final AtomicLong bulkheadRejectedCalls = new AtomicLong();
 
     public FaultToleranceProcessor(
                                    FaultToleranceConfiguration config,
@@ -225,6 +229,21 @@ public class FaultToleranceProcessor extends 
BaseProcessorSupport
         return notPermittedCalls.get();
     }
 
+    @ManagedAttribute(description = "Returns the number of calls answered by 
the onFallback (failed, timed out and rejected calls alike)")
+    public long getNumberOfFallbackCalls() {
+        return fallbackCalls.get();
+    }
+
+    @ManagedAttribute(description = "Returns the number of calls that timed 
out (the circuit breaker counts them as failed calls)")
+    public long getNumberOfTimedOutCalls() {
+        return timedOutCalls.get();
+    }
+
+    @ManagedAttribute(description = "Returns the number of calls rejected 
because the bulkhead was full (not included in the not permitted calls)")
+    public long getNumberOfBulkheadRejectedCalls() {
+        return bulkheadRejectedCalls.get();
+    }
+
     @ManagedOperation(description = "Resets the circuit breaker to CLOSED 
state and clears call counters.")
     public void transitionToCloseState() {
         try {
@@ -232,6 +251,9 @@ public class FaultToleranceProcessor extends 
BaseProcessorSupport
             successfulCalls.set(0);
             failedCalls.set(0);
             notPermittedCalls.set(0);
+            fallbackCalls.set(0);
+            timedOutCalls.set(0);
+            bulkheadRejectedCalls.set(0);
         } catch (Exception e) {
             // ignored
         }
@@ -293,11 +315,20 @@ public class FaultToleranceProcessor extends 
BaseProcessorSupport
             
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_REJECTED, 
true);
         } catch (TimeoutException e) {
             // the circuit breaker triggered a timeout (no fallback)
+            timedOutCalls.incrementAndGet();
             
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SUCCESSFUL_EXECUTION,
 false);
             
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_FROM_FALLBACK,
 false);
             
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SHORT_CIRCUITED,
 false);
             
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_TIMED_OUT, 
true);
             exchange.setException(e);
+        } catch (BulkheadException e) {
+            // the bulkhead is full (no fallback)
+            bulkheadRejectedCalls.incrementAndGet();
+            
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SUCCESSFUL_EXECUTION,
 false);
+            
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_FROM_FALLBACK,
 false);
+            
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SHORT_CIRCUITED,
 true);
+            
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_REJECTED, 
true);
+            exchange.setException(e);
         } catch (Exception e) {
             // some other kind of exception
             exchange.setException(e);
@@ -571,7 +602,12 @@ public class FaultToleranceProcessor extends 
BaseProcessorSupport
             boolean rejected = exchange.getException() instanceof 
CircuitBreakerOpenException
                     || exchange.getException() instanceof BulkheadException;
             
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_REJECTED, 
rejected);
+            fallbackCalls.incrementAndGet();
+            if (exchange.getException() instanceof BulkheadException) {
+                bulkheadRejectedCalls.incrementAndGet();
+            }
             if (exchange.getException() instanceof TimeoutException) {
+                timedOutCalls.incrementAndGet();
                 
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_TIMED_OUT, 
true);
             }
 
diff --git 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java
 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java
new file mode 100644
index 000000000000..4ac9baf2da8d
--- /dev/null
+++ 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceCallCountersTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit 
breaker listeners do not tell apart.
+ */
+public class FaultToleranceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without 
being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbDown", 
FaultToleranceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback 
response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        FaultToleranceProcessor cb = context.getProcessor("cbSlow", 
FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the 
fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call 
should be inside the bulkhead");
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        // the rejected call reached the fallback with the bulkhead exception 
(and came back first,
+        // before the slow call finished), so it was rejected, not queued
+        long rejected = 
getMockEndpoint("mock:bulkhead").getExchanges().stream()
+                .filter(e -> e.getProperty(Exchange.EXCEPTION_CAUGHT) 
instanceof BulkheadException).count();
+        assertEquals(1, rejected, "exactly one of the two calls should have 
been rejected by the bulkhead");
+        FaultToleranceProcessor cb = context.getProcessor("cbBulkhead", 
FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Test
+    public void testTimedOutWithoutFallbackCounter() throws Exception {
+        // no onFallback: the timeout fails the exchange, and the counter is 
kept on that path too
+        Exchange result = template.request("direct:slowNoFallback", e -> 
e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the timed out call should fail the 
exchange");
+        FaultToleranceProcessor cb = context.getProcessor("cbSlowNoFallback", 
FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithoutFallbackCounter() throws Exception {
+        // no onFallback: the second call is rejected by the bulkhead and 
fails the exchange
+        template.asyncSendBody("direct:bulkheadNoFallback", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call 
should be inside the bulkhead");
+        Exchange result = template.request("direct:bulkheadNoFallback", e -> 
e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the rejected call should fail the 
exchange");
+        // SmallRye needs a waiting queue of at least 1; the second call is 
still rejected, not queued
+        assertInstanceOf(BulkheadException.class, result.getException());
+        FaultToleranceProcessor cb = 
context.getProcessor("cbBulkheadNoFallback", FaultToleranceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:down")
+                        .circuitBreaker().id("cbDown")
+                        .faultToleranceConfiguration()
+                        
.requestVolumeThreshold(2).failureRatio(50).delay(60000).end()
+                        .throwException(new IllegalStateException("Service is 
down"))
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:down");
+
+                from("direct:slow")
+                        .circuitBreaker().id("cbSlow")
+                        
.faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:slow");
+
+                from("direct:slowService")
+                        .delay(2000).transform().constant("Slow response");
+
+                from("direct:bulkhead")
+                        .circuitBreaker().id("cbBulkhead")
+                        .faultToleranceConfiguration()
+                        
.bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(1).end()
+                        .process(e -> permitAcquired.countDown())
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:bulkhead");
+
+                from("direct:slowNoFallback")
+                        .circuitBreaker().id("cbSlowNoFallback")
+                        
.faultToleranceConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .end();
+
+                from("direct:bulkheadNoFallback")
+                        .circuitBreaker().id("cbBulkheadNoFallback")
+                        
.faultToleranceConfiguration().bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadWaitingTaskQueue(1).end()
+                        .process(e -> permitAcquired.countDown())
+                        .to("direct:slowService")
+                        .end();
+            }
+        };
+    }
+}
diff --git 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsoleTest.java
 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsoleTest.java
index e4ab2f34270e..37f097687dc0 100644
--- 
a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsoleTest.java
+++ 
b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceConsoleTest.java
@@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class FaultToleranceConsoleTest extends CamelTestSupport {
 
@@ -52,6 +53,7 @@ public class FaultToleranceConsoleTest extends 
CamelTestSupport {
 
         String text = (String) con.call(DevConsole.MediaType.TEXT);
         assertNotNull(text);
+        assertTrue(text.contains("fallback: 0 timed-out: 0 bulkhead-rejected: 
0"), text);
     }
 
     @Test
@@ -70,6 +72,9 @@ public class FaultToleranceConsoleTest extends 
CamelTestSupport {
         assertEquals("myBreaker", entry.getString("id"));
         assertEquals("myRoute", entry.getString("routeId"));
         assertNotNull(entry.getString("state"));
+        assertEquals(0L, entry.getLong("fallbackCalls"));
+        assertEquals(0L, entry.getLong("timedOutCalls"));
+        assertEquals(0L, entry.getLong("bulkheadRejectedCalls"));
 
         JsonObject config = entry.getJsonObject("configuration");
         assertNotNull(config);
diff --git 
a/components/camel-resilience4j/src/generated/resources/META-INF/org/apache/camel/dev-console/resilience4j.json
 
b/components/camel-resilience4j/src/generated/resources/META-INF/org/apache/camel/dev-console/resilience4j.json
index d1553e7e2fa5..210626297a5a 100644
--- 
a/components/camel-resilience4j/src/generated/resources/META-INF/org/apache/camel/dev-console/resilience4j.json
+++ 
b/components/camel-resilience4j/src/generated/resources/META-INF/org/apache/camel/dev-console/resilience4j.json
@@ -48,6 +48,18 @@
               "type": "integer",
               "description": "The number of not permitted calls"
             },
+            "fallbackCalls": {
+              "type": "integer",
+              "description": "The number of calls answered by the onFallback"
+            },
+            "timedOutCalls": {
+              "type": "integer",
+              "description": "The number of calls that timed out"
+            },
+            "bulkheadRejectedCalls": {
+              "type": "integer",
+              "description": "The number of calls rejected because the 
bulkhead was full"
+            },
             "failureRate": {
               "type": "number",
               "description": "The failure rate in percentage"
@@ -127,6 +139,9 @@
             "successfulCalls",
             "failedCalls",
             "notPermittedCalls",
+            "fallbackCalls",
+            "timedOutCalls",
+            "bulkheadRejectedCalls",
             "failureRate"
           ]
         },
diff --git 
a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceConsole.java
 
b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceConsole.java
index beee615e74cb..d8c6fecf43f4 100644
--- 
a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceConsole.java
+++ 
b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceConsole.java
@@ -56,6 +56,9 @@ public class ResilienceConsole extends AbstractDevConsole {
             @Metadata(description = "The number of successful calls") int 
successfulCalls,
             @Metadata(description = "The number of failed calls") int 
failedCalls,
             @Metadata(description = "The number of not permitted calls") long 
notPermittedCalls,
+            @Metadata(description = "The number of calls answered by the 
onFallback") long fallbackCalls,
+            @Metadata(description = "The number of calls that timed out") long 
timedOutCalls,
+            @Metadata(description = "The number of calls rejected because the 
bulkhead was full") long bulkheadRejectedCalls,
             @Metadata(description = "The failure rate in percentage") float 
failureRate,
             @Metadata(description = "The circuit breaker configuration") 
Configuration configuration) {
     }
@@ -91,13 +94,18 @@ public class ResilienceConsole extends AbstractDevConsole {
             int bc = cb.getNumberOfBufferedCalls();
             int fc = cb.getNumberOfFailedCalls();
             long npc = cb.getNumberOfNotPermittedCalls();
+            long fb = cb.getNumberOfFallbackCalls();
+            long to = cb.getNumberOfTimedOutCalls();
+            long br = cb.getNumberOfBulkheadRejectedCalls();
             float fr = cb.getFailureRate();
             if (fr >= 0) {
-                sb.append(String.format("    %s/%s: %s (buffered: %d success: 
%d failure: %d/%.0f%% not-permitted: %d)%n", rid,
-                        id, state, bc, sc, fc, fr, npc));
+                sb.append(String.format(
+                        "    %s/%s: %s (buffered: %d success: %d failure: 
%d/%.0f%% not-permitted: %d fallback: %d timed-out: %d bulkhead-rejected: 
%d)%n",
+                        rid, id, state, bc, sc, fc, fr, npc, fb, to, br));
             } else {
-                sb.append(String.format("    %s/%s: %s (buffered: %d success: 
%d failure: %d not-permitted: %d)%n", rid, id,
-                        state, bc, sc, fc, npc));
+                sb.append(String.format(
+                        "    %s/%s: %s (buffered: %d success: %d failure: %d 
not-permitted: %d fallback: %d timed-out: %d bulkhead-rejected: %d)%n",
+                        rid, id, state, bc, sc, fc, npc, fb, to, br));
             }
         }
 
@@ -133,6 +141,7 @@ public class ResilienceConsole extends AbstractDevConsole {
             list.add(new CircuitBreakerEntry(
                     cb.getId(), cb.getRouteId(), cb.getCircuitBreakerState(), 
cb.getNumberOfBufferedCalls(),
                     cb.getNumberOfSuccessfulCalls(), 
cb.getNumberOfFailedCalls(), cb.getNumberOfNotPermittedCalls(),
+                    cb.getNumberOfFallbackCalls(), 
cb.getNumberOfTimedOutCalls(), cb.getNumberOfBulkheadRejectedCalls(),
                     cb.getFailureRate(), config));
         }
 
diff --git 
a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
 
b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
index 81c41f14ae49..c4cd72ed7b1e 100644
--- 
a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
+++ 
b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
@@ -27,6 +27,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -109,6 +110,10 @@ public class ResilienceProcessor extends 
BaseProcessorSupport
     private ProcessorExchangeFactory processorExchangeFactory;
     private PooledExchangeTaskFactory taskFactory;
     private PooledExchangeTaskFactory fallbackTaskFactory;
+    // counters for what the circuit breaker metrics do not tell apart
+    private final AtomicLong fallbackCalls = new AtomicLong();
+    private final AtomicLong timedOutCalls = new AtomicLong();
+    private final AtomicLong bulkheadRejectedCalls = new AtomicLong();
 
     public ResilienceProcessor(CircuitBreakerConfig circuitBreakerConfig, 
BulkheadConfig bulkheadConfig,
                                TimeLimiterConfig timeLimiterConfig, Processor 
processor,
@@ -400,6 +405,21 @@ public class ResilienceProcessor extends 
BaseProcessorSupport
         }
     }
 
+    @ManagedAttribute(description = "Returns the number of calls answered by 
the onFallback (failed, timed out and rejected calls alike).")
+    public long getNumberOfFallbackCalls() {
+        return fallbackCalls.get();
+    }
+
+    @ManagedAttribute(description = "Returns the number of calls that timed 
out (the circuit breaker counts them as failed calls).")
+    public long getNumberOfTimedOutCalls() {
+        return timedOutCalls.get();
+    }
+
+    @ManagedAttribute(description = "Returns the number of calls rejected 
because the bulkhead was full (not included in the not permitted calls).")
+    public long getNumberOfBulkheadRejectedCalls() {
+        return bulkheadRejectedCalls.get();
+    }
+
     @ManagedAttribute(description = "Returns the current state of the circuit 
breaker")
     public String getCircuitBreakerState() {
         if (circuitBreaker != null) {
@@ -409,11 +429,14 @@ public class ResilienceProcessor extends 
BaseProcessorSupport
         }
     }
 
-    @ManagedOperation(description = "Transitions the circuit breaker to CLOSED 
state.")
+    @ManagedOperation(description = "Transitions the circuit breaker to CLOSED 
state and resets the fallback, timed out and bulkhead rejected call counters.")
     public void transitionToCloseState() {
         if (circuitBreaker != null) {
             circuitBreaker.transitionToClosedState();
         }
+        fallbackCalls.set(0);
+        timedOutCalls.set(0);
+        bulkheadRejectedCalls.set(0);
     }
 
     @ManagedOperation(description = "Transitions the circuit breaker to OPEN 
state.")
@@ -919,6 +942,7 @@ public class ResilienceProcessor extends 
BaseProcessorSupport
                 if (throwable instanceof TimeoutException) {
                     // the circuit breaker triggered a timeout (and there is no
                     // fallback) so lets mark the exchange as failed
+                    timedOutCalls.incrementAndGet();
                     
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SUCCESSFUL_EXECUTION,
 false);
                     
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_FROM_FALLBACK,
 false);
                     
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SHORT_CIRCUITED,
 false);
@@ -937,6 +961,7 @@ public class ResilienceProcessor extends 
BaseProcessorSupport
                     return exchange;
                 } else if (throwable instanceof BulkheadFullException) {
                     // the circuit breaker bulkhead is full
+                    bulkheadRejectedCalls.incrementAndGet();
                     
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SUCCESSFUL_EXECUTION,
 false);
                     
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_FROM_FALLBACK,
 false);
                     
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_SHORT_CIRCUITED,
 true);
@@ -962,7 +987,12 @@ public class ResilienceProcessor extends 
BaseProcessorSupport
             // so the fallback can tell that apart from a call that was made 
and failed
             boolean rejected = throwable instanceof CallNotPermittedException 
|| throwable instanceof BulkheadFullException;
             
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_REJECTED, 
rejected);
+            fallbackCalls.incrementAndGet();
+            if (throwable instanceof BulkheadFullException) {
+                bulkheadRejectedCalls.incrementAndGet();
+            }
             if (throwable instanceof TimeoutException) {
+                timedOutCalls.incrementAndGet();
                 
exchange.setProperty(ExchangePropertyKey.CIRCUIT_BREAKER_RESPONSE_TIMED_OUT, 
true);
             }
 
diff --git 
a/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceCallCountersTest.java
 
b/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceCallCountersTest.java
new file mode 100644
index 000000000000..2ebd72d34ce8
--- /dev/null
+++ 
b/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceCallCountersTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.component.resilience4j;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The fallback, timed out and bulkhead rejected counters, which the circuit 
breaker metrics do not tell apart.
+ */
+public class ResilienceCallCountersTest extends CamelTestSupport {
+
+    private final CountDownLatch permitAcquired = new CountDownLatch(1);
+
+    @Test
+    public void testFallbackAndRejectedCounters() throws Exception {
+        getMockEndpoint("mock:down").expectedMessageCount(3);
+
+        // two failures open the breaker, the third call is rejected without 
being attempted
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+        template.sendBody("direct:down", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        ResilienceProcessor cb = context.getProcessor("cbDown", 
ResilienceProcessor.class);
+        assertEquals(3, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfBulkheadRejectedCalls());
+
+        cb.transitionToCloseState();
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testTimedOutCounter() throws Exception {
+        getMockEndpoint("mock:slow").expectedBodiesReceived("Fallback 
response");
+
+        template.sendBody("direct:slow", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        ResilienceProcessor cb = context.getProcessor("cbSlow", 
ResilienceProcessor.class);
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithFallbackCounter() throws Exception {
+        getMockEndpoint("mock:bulkhead").expectedMessageCount(2);
+
+        // the first call holds the only bulkhead permit while it is slow,
+        // so the second call is rejected by the bulkhead and answered by the 
fallback
+        template.asyncSendBody("direct:bulkhead", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call 
should be inside the bulkhead");
+        template.sendBody("direct:bulkhead", "Hello World");
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        ResilienceProcessor cb = context.getProcessor("cbBulkhead", 
ResilienceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls(),
+                "the rejected call is counted from inside the fallback path");
+        assertEquals(1, cb.getNumberOfFallbackCalls());
+        assertEquals(0, cb.getNumberOfNotPermittedCalls());
+        assertEquals(0, cb.getNumberOfTimedOutCalls());
+    }
+
+    @Test
+    public void testTimedOutWithoutFallbackCounter() throws Exception {
+        // no onFallback: the timeout fails the exchange, and the counter is 
kept on that path too
+        Exchange result = template.request("direct:slowNoFallback", e -> 
e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the timed out call should fail the 
exchange");
+        ResilienceProcessor cb = context.getProcessor("cbSlowNoFallback", 
ResilienceProcessor.class);
+        assertEquals(1, cb.getNumberOfTimedOutCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Test
+    public void testBulkheadRejectedWithoutFallbackCounter() throws Exception {
+        // no onFallback: the second call is rejected by the bulkhead and 
fails the exchange
+        template.asyncSendBody("direct:bulkheadNoFallback", "Hello World");
+        assertTrue(permitAcquired.await(5, TimeUnit.SECONDS), "first call 
should be inside the bulkhead");
+        Exchange result = template.request("direct:bulkheadNoFallback", e -> 
e.getMessage().setBody("Hello World"));
+
+        assertTrue(result.isFailed(), "the rejected call should fail the 
exchange");
+        ResilienceProcessor cb = context.getProcessor("cbBulkheadNoFallback", 
ResilienceProcessor.class);
+        assertEquals(1, cb.getNumberOfBulkheadRejectedCalls());
+        assertEquals(0, cb.getNumberOfFallbackCalls());
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:down")
+                        .circuitBreaker().id("cbDown")
+                        .resilience4jConfiguration()
+                        
.slidingWindowSize(2).minimumNumberOfCalls(2).failureRateThreshold(50)
+                        .waitDurationInOpenState(60).end()
+                        .throwException(new IllegalStateException("Service is 
down"))
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:down");
+
+                from("direct:slow")
+                        .circuitBreaker().id("cbSlow")
+                        
.resilience4jConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:slow");
+
+                from("direct:slowService")
+                        .delay(2000).transform().constant("Slow response");
+
+                from("direct:bulkhead")
+                        .circuitBreaker().id("cbBulkhead")
+                        .resilience4jConfiguration()
+                        
.bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadMaxWaitDuration(0).end()
+                        .process(e -> permitAcquired.countDown())
+                        .to("direct:slowService")
+                        .onFallback()
+                        .transform().constant("Fallback response")
+                        .end()
+                        .to("mock:bulkhead");
+
+                from("direct:slowNoFallback")
+                        .circuitBreaker().id("cbSlowNoFallback")
+                        
.resilience4jConfiguration().timeoutEnabled(true).timeoutDuration(200).end()
+                        .to("direct:slowService")
+                        .end();
+
+                from("direct:bulkheadNoFallback")
+                        .circuitBreaker().id("cbBulkheadNoFallback")
+                        
.resilience4jConfiguration().bulkheadEnabled(true).bulkheadMaxConcurrentCalls(1).bulkheadMaxWaitDuration(0).end()
+                        .process(e -> permitAcquired.countDown())
+                        .to("direct:slowService")
+                        .end();
+            }
+        };
+    }
+}
diff --git 
a/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceConsoleTest.java
 
b/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceConsoleTest.java
index b300134cb59d..bdbe5c07b7c6 100644
--- 
a/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceConsoleTest.java
+++ 
b/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceConsoleTest.java
@@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class ResilienceConsoleTest extends CamelTestSupport {
 
@@ -52,6 +53,7 @@ public class ResilienceConsoleTest extends CamelTestSupport {
 
         String text = (String) con.call(DevConsole.MediaType.TEXT);
         assertNotNull(text);
+        assertTrue(text.contains("fallback: 0 timed-out: 0 bulkhead-rejected: 
0"), text);
     }
 
     @Test
@@ -70,6 +72,9 @@ public class ResilienceConsoleTest extends CamelTestSupport {
         assertEquals("myBreaker", entry.getString("id"));
         assertEquals("myRoute", entry.getString("routeId"));
         assertNotNull(entry.getString("state"));
+        assertEquals(0L, entry.getLong("fallbackCalls"));
+        assertEquals(0L, entry.getLong("timedOutCalls"));
+        assertEquals(0L, entry.getLong("bulkheadRejectedCalls"));
 
         JsonObject config = entry.getJsonObject("configuration");
         assertNotNull(config);
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index b7ebddc1a4d7..67c0068b8780 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -640,10 +640,15 @@ View the state of 
xref:components:eips:circuitBreaker-eip.adoc[Circuit Breakers]
 [source,bash]
 ----
 camel get circuit-breaker
-  PID   NAME  COMPONENT     ROUTE   ID               STATE      PENDING  
SUCCESS  FAIL  REJECT
- 56033  mycb  resilience4j  route1  circuitBreaker1  HALF_OPEN        5        
2     3       0
+  PID   NAME  COMPONENT     ROUTE   ID               STATE      PENDING  
SUCCESS  FAIL  REJECT  FALLBACK  TIMEOUT
+ 56033  mycb  resilience4j  route1  circuitBreaker1  HALF_OPEN        5        
2     3       0         3        1
 ----
 
+REJECT counts the calls the open breaker refused without calling the service, 
FALLBACK the calls
+the `onFallback` answered (failed, timed out and rejected calls alike), and 
TIMEOUT the calls that hit
+the configured timeout, which the breaker otherwise counts as plain failures. 
A BULKHEAD column with
+the calls rejected by a full bulkhead is shown when a bulkhead is configured.
+
 TIP: Use `camel get circuit-breaker --watch` for continuous updates.
 
 == Using Jolokia and Hawtio
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreaker.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreaker.java
index 315234f0670e..c8224f96d4bd 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreaker.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreaker.java
@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
 
 import com.github.freva.asciitable.AsciiTable;
 import com.github.freva.asciitable.Column;
+import com.github.freva.asciitable.ColumnData;
 import com.github.freva.asciitable.HorizontalAlign;
 import com.github.freva.asciitable.OverflowBehaviour;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
@@ -94,6 +95,10 @@ public class ListCircuitBreaker extends ProcessWatchCommand {
                                     row.successfulCalls = 
jo.getInteger("successfulCalls");
                                     row.failedCalls = 
jo.getInteger("failedCalls");
                                     row.notPermittedCalls = 
jo.getLong("notPermittedCalls");
+                                    row.fallbackCalls = 
jo.getLongOrDefault("fallbackCalls", 0);
+                                    row.timedOutCalls = 
jo.getLongOrDefault("timedOutCalls", 0);
+                                    row.bulkheadRejectedCalls = 
jo.getLongOrDefault("bulkheadRejectedCalls", 0);
+                                    row.bulkheadEnabled = 
isBulkheadEnabled(jo);
                                     row.failureRate = 
jo.getDouble("failureRate");
                                     rows.add(row);
                                 }
@@ -110,6 +115,14 @@ public class ListCircuitBreaker extends 
ProcessWatchCommand {
                                     row.id = jo.getString("id");
                                     row.routeId = jo.getString("routeId");
                                     row.state = jo.getString("state");
+                                    row.successfulCalls = 
jo.getLongOrDefault("successfulCalls", 0).intValue();
+                                    row.failedCalls = 
jo.getLongOrDefault("failedCalls", 0).intValue();
+                                    row.notPermittedCalls = 
jo.getLongOrDefault("notPermittedCalls", 0);
+                                    row.fallbackCalls = 
jo.getLongOrDefault("fallbackCalls", 0);
+                                    row.timedOutCalls = 
jo.getLongOrDefault("timedOutCalls", 0);
+                                    row.bulkheadRejectedCalls = 
jo.getLongOrDefault("bulkheadRejectedCalls", 0);
+                                    row.bulkheadEnabled = 
isBulkheadEnabled(jo);
+                                    row.failureRate = -1;
                                     rows.add(row);
                                 }
                             }
@@ -151,27 +164,47 @@ public class ListCircuitBreaker extends 
ProcessWatchCommand {
                     jo.put("successfulCalls", r.successfulCalls);
                     jo.put("failedCalls", r.failedCalls);
                     jo.put("notPermittedCalls", r.notPermittedCalls);
+                    jo.put("fallbackCalls", r.fallbackCalls);
+                    jo.put("timedOutCalls", r.timedOutCalls);
+                    jo.put("bulkheadRejectedCalls", r.bulkheadRejectedCalls);
                     jo.put("failureRate", r.failureRate);
                     return jo;
                 }).collect(Collectors.toList())));
             } else {
-                printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, 
rows, Arrays.asList(
-                        new 
Column().header("PID").headerAlign(HorizontalAlign.CENTER).with(r -> r.pid),
-                        new 
Column().header("NAME").dataAlign(HorizontalAlign.LEFT)
-                                .maxWidth(30, OverflowBehaviour.ELLIPSIS_RIGHT)
-                                .with(r -> r.name),
-                        new 
Column().header("COMPONENT").dataAlign(HorizontalAlign.LEFT).with(r -> 
r.component),
-                        new 
Column().header("ROUTE").dataAlign(HorizontalAlign.LEFT).with(r -> r.routeId),
-                        new 
Column().header("ID").dataAlign(HorizontalAlign.LEFT).with(r -> r.id),
-                        new 
Column().header("STATE").dataAlign(HorizontalAlign.LEFT).with(r -> r.state),
-                        new 
Column().header("PENDING").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
-                                .with(this::getPending),
-                        new 
Column().header("SUCCESS").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
-                                .with(this::getSuccess),
-                        new 
Column().header("FAIL").headerAlign(HorizontalAlign.CENTER).dataAlign(HorizontalAlign.RIGHT)
-                                .with(this::getFailure),
-                        new 
Column().header("REJECT").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
-                                .with(this::getReject))));
+                List<ColumnData<Row>> columns = new ArrayList<>(
+                        Arrays.asList(
+                                new 
Column().header("PID").headerAlign(HorizontalAlign.CENTER).with(r -> r.pid),
+                                new 
Column().header("NAME").dataAlign(HorizontalAlign.LEFT)
+                                        .maxWidth(30, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                                        .with(r -> r.name),
+                                new 
Column().header("COMPONENT").dataAlign(HorizontalAlign.LEFT).with(r -> 
r.component),
+                                new 
Column().header("ROUTE").dataAlign(HorizontalAlign.LEFT).with(r -> r.routeId),
+                                new 
Column().header("ID").dataAlign(HorizontalAlign.LEFT).with(r -> r.id),
+                                new 
Column().header("STATE").dataAlign(HorizontalAlign.LEFT).with(r -> r.state),
+                                new 
Column().header("PENDING").headerAlign(HorizontalAlign.RIGHT)
+                                        .dataAlign(HorizontalAlign.RIGHT)
+                                        .with(this::getPending),
+                                new 
Column().header("SUCCESS").headerAlign(HorizontalAlign.RIGHT)
+                                        .dataAlign(HorizontalAlign.RIGHT)
+                                        .with(this::getSuccess),
+                                new 
Column().header("FAIL").headerAlign(HorizontalAlign.CENTER).dataAlign(HorizontalAlign.RIGHT)
+                                        .with(this::getFailure),
+                                new 
Column().header("REJECT").headerAlign(HorizontalAlign.RIGHT)
+                                        .dataAlign(HorizontalAlign.RIGHT)
+                                        .with(this::getReject),
+                                new 
Column().header("FALLBACK").headerAlign(HorizontalAlign.RIGHT)
+                                        .dataAlign(HorizontalAlign.RIGHT)
+                                        .with(r -> countOrBlank(r, 
r.fallbackCalls)),
+                                new 
Column().header("TIMEOUT").headerAlign(HorizontalAlign.RIGHT)
+                                        .dataAlign(HorizontalAlign.RIGHT)
+                                        .with(r -> countOrBlank(r, 
r.timedOutCalls))));
+                // the bulkhead column only makes sense when a bulkhead is 
configured
+                if (rows.stream().anyMatch(r -> r.bulkheadEnabled)) {
+                    columns.add(new 
Column().header("BULKHEAD").headerAlign(HorizontalAlign.RIGHT)
+                            .dataAlign(HorizontalAlign.RIGHT)
+                            .with(r -> countOrBlank(r, 
r.bulkheadRejectedCalls)));
+                }
+                printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, 
rows, columns));
             }
         }
 
@@ -215,19 +248,31 @@ public class ListCircuitBreaker extends 
ProcessWatchCommand {
     }
 
     private String getSuccess(Row r) {
-        if ("resilience4j".equals(r.component) || "core".equals(r.component)) {
+        if ("resilience4j".equals(r.component) || 
"fault-tolerance".equals(r.component) || "core".equals(r.component)) {
             return Integer.toString(r.successfulCalls);
         }
         return "";
     }
 
     private String getReject(Row r) {
-        if ("resilience4j".equals(r.component)) {
+        if ("resilience4j".equals(r.component) || 
"fault-tolerance".equals(r.component)) {
             return Long.toString(r.notPermittedCalls);
         }
         return "";
     }
 
+    private String countOrBlank(Row r, long count) {
+        if ("resilience4j".equals(r.component) || 
"fault-tolerance".equals(r.component)) {
+            return Long.toString(count);
+        }
+        return "";
+    }
+
+    private static boolean isBulkheadEnabled(JsonObject jo) {
+        JsonObject cfg = (JsonObject) jo.get("configuration");
+        return cfg != null && Boolean.TRUE.equals(cfg.get("bulkheadEnabled"));
+    }
+
     private static class Row implements Cloneable {
         String pid;
         String name;
@@ -241,6 +286,10 @@ public class ListCircuitBreaker extends 
ProcessWatchCommand {
         int successfulCalls;
         int failedCalls;
         long notPermittedCalls;
+        long fallbackCalls;
+        long timedOutCalls;
+        long bulkheadRejectedCalls;
+        boolean bulkheadEnabled;
         double failureRate;
 
         Row copy() {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java
index 32c73e1dc511..c6472dbdd13a 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java
@@ -28,6 +28,7 @@ import org.mockito.MockedStatic;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mockStatic;
 
@@ -78,6 +79,9 @@ class ListCircuitBreakerTest extends 
ProcessCommandTestSupport {
             String output = printer.getOutput();
             assertTrue(output.contains("myCB"), "Should show circuit breaker 
ID");
             assertTrue(output.contains("CLOSED"), "Should show CLOSED state");
+            assertTrue(output.contains("FALLBACK"), "Should show FALLBACK 
column");
+            assertTrue(output.contains("TIMEOUT"), "Should show TIMEOUT 
column");
+            assertFalse(output.contains("BULKHEAD"), "Should not show BULKHEAD 
column without a bulkhead");
         }
     }
 
@@ -130,6 +134,33 @@ class ListCircuitBreakerTest extends 
ProcessCommandTestSupport {
         }
     }
 
+    @Test
+    void testShowsFaultToleranceCountersAndBulkheadColumn() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("fault-tolerance", cbContainer(faultToleranceEntry("OPEN")));
+        writeStatusFile(TEST_PID, root);
+
+        ListCircuitBreaker command = new ListCircuitBreaker(new 
CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = 
mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> 
Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("myFtCB"), "Should show circuit breaker 
ID");
+            assertTrue(output.contains("BULKHEAD"), "Should show BULKHEAD 
column when a bulkhead is configured");
+            String row = output.lines().filter(l -> 
l.contains("myFtCB")).findFirst().orElseThrow();
+            assertTrue(row.matches(".*\\b4\\s+1\\s+3\\s+4\\s+0\\s+5\\s*$"), 
row);
+        }
+    }
+
     private static JsonObject contextObj() {
         JsonObject ctx = new JsonObject();
         ctx.put("name", "myApp");
@@ -153,7 +184,27 @@ class ListCircuitBreakerTest extends 
ProcessCommandTestSupport {
         cb.put("successfulCalls", 0);
         cb.put("failedCalls", 0);
         cb.put("notPermittedCalls", 0L);
+        cb.put("fallbackCalls", 7L);
+        cb.put("timedOutCalls", 2L);
+        cb.put("bulkheadRejectedCalls", 0L);
         cb.put("failureRate", 0.0);
         return cb;
     }
+
+    private static JsonObject faultToleranceEntry(String state) {
+        JsonObject cb = new JsonObject();
+        cb.put("id", "myFtCB");
+        cb.put("routeId", "myRoute");
+        cb.put("state", state);
+        cb.put("successfulCalls", 4L);
+        cb.put("failedCalls", 1L);
+        cb.put("notPermittedCalls", 3L);
+        cb.put("fallbackCalls", 4L);
+        cb.put("timedOutCalls", 0L);
+        cb.put("bulkheadRejectedCalls", 5L);
+        JsonObject cfg = new JsonObject();
+        cfg.put("bulkheadEnabled", true);
+        cb.put("configuration", cfg);
+        return cb;
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerInfo.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerInfo.java
index 843ab0c68dcd..3213125370c6 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerInfo.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerInfo.java
@@ -25,6 +25,9 @@ class CircuitBreakerInfo {
     long successfulCalls;
     long failedCalls;
     long notPermittedCalls;
+    long fallbackCalls;
+    long timedOutCalls;
+    long bulkheadRejectedCalls;
     double failureRate;
     long total;
     long totalFailed;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
index a13ad4f917fc..ec8e392f9dae 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
@@ -87,6 +87,8 @@ class CircuitBreakerTab extends AbstractTableTab {
             String success = cb.successfulCalls > 0 ? 
String.valueOf(cb.successfulCalls) : "";
             String failed = cb.failedCalls > 0 ? 
String.valueOf(cb.failedCalls) : "";
             String reject = cb.notPermittedCalls > 0 ? 
String.valueOf(cb.notPermittedCalls) : "";
+            String fallback = cb.fallbackCalls > 0 ? 
String.valueOf(cb.fallbackCalls) : "";
+            String timeout = cb.timedOutCalls > 0 ? 
String.valueOf(cb.timedOutCalls) : "";
             String rate = cb.failureRate >= 0 ? String.format("%.0f%%", 
cb.failureRate) : "";
             String inflight = String.valueOf(cb.inflight);
             String sinceLast = formatSinceLast(cb.sinceLastStarted, 
cb.sinceLastSuccess, cb.sinceLastFail);
@@ -102,11 +104,13 @@ class CircuitBreakerTab extends AbstractTableTab {
                     rightCell(failed, 8),
                     rightCell(rate, 6),
                     rightCell(reject, 8),
+                    rightCell(fallback, 8),
+                    rightCell(timeout, 8),
                     Cell.from(sinceLast)));
         }
 
         if (rows.isEmpty()) {
-            rows.add(emptyRow("No circuit breakers", 11));
+            rows.add(emptyRow("No circuit breakers", 13));
         }
 
         CircuitBreakerInfo selectedCb = null;
@@ -134,6 +138,8 @@ class CircuitBreakerTab extends AbstractTableTab {
                         rightCell("FAIL", 8, Style.EMPTY.bold()),
                         rightCell("RATE%", 6, Style.EMPTY.bold()),
                         rightCell("REJECT", 8, Style.EMPTY.bold()),
+                        rightCell("FALLBACK", 8, Style.EMPTY.bold()),
+                        rightCell("TIMEOUT", 8, Style.EMPTY.bold()),
                         Cell.from(Span.styled("SINCE-LAST", 
Style.EMPTY.bold()))))
                 .widths(
                         Constraint.length(20),
@@ -146,6 +152,8 @@ class CircuitBreakerTab extends AbstractTableTab {
                         Constraint.length(8),
                         Constraint.length(6),
                         Constraint.length(8),
+                        Constraint.length(8),
+                        Constraint.length(8),
                         Constraint.fill())
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
@@ -345,6 +353,9 @@ class CircuitBreakerTab extends AbstractTableTab {
                 Span.styled("fail:", dim), Span.raw(cb.totalFailed + " "),
                 Span.styled("inflight:", dim), Span.raw(cb.inflight + " "),
                 Span.styled("reject:", dim), Span.raw(cb.notPermittedCalls + " 
"),
+                Span.styled("fallback:", dim), Span.raw(cb.fallbackCalls + " 
"),
+                Span.styled("timeout:", dim), Span.raw(cb.timedOutCalls + " "),
+                Span.styled("bulkhead:", dim), 
Span.raw(cb.bulkheadRejectedCalls + " "),
                 Span.styled("mean:", dim), Span.raw(cb.meanTime + "ms "),
                 Span.styled("min:", dim), Span.raw(cb.minTime + "ms "),
                 Span.styled("max:", dim), Span.raw(cb.maxTime + "ms"));
@@ -406,6 +417,9 @@ class CircuitBreakerTab extends AbstractTableTab {
             row.put("successfulCalls", cb.successfulCalls);
             row.put("failedCalls", cb.failedCalls);
             row.put("notPermittedCalls", cb.notPermittedCalls);
+            row.put("fallbackCalls", cb.fallbackCalls);
+            row.put("timedOutCalls", cb.timedOutCalls);
+            row.put("bulkheadRejectedCalls", cb.bulkheadRejectedCalls);
             row.put("failureRate", cb.failureRate);
             row.put("total", cb.total);
             row.put("totalFailed", cb.totalFailed);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index 493d06d1733f..da3e27a8eab8 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -1288,6 +1288,9 @@ final class StatusParser {
             cb.successfulCalls = 
TuiHelper.objToLong(bj.get("successfulCalls"));
             cb.failedCalls = TuiHelper.objToLong(bj.get("failedCalls"));
             cb.notPermittedCalls = 
TuiHelper.objToLong(bj.get("notPermittedCalls"));
+            cb.fallbackCalls = TuiHelper.objToLong(bj.get("fallbackCalls"));
+            cb.timedOutCalls = TuiHelper.objToLong(bj.get("timedOutCalls"));
+            cb.bulkheadRejectedCalls = 
TuiHelper.objToLong(bj.get("bulkheadRejectedCalls"));
             Object fr = bj.get("failureRate");
             cb.failureRate = fr instanceof Number n ? n.doubleValue() : -1;
             info.circuitBreakers.add(cb);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/circuit-breaker.md
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/circuit-breaker.md
index ceb2c8e7273d..2f167fa7117e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/circuit-breaker.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/circuit-breaker.md
@@ -48,20 +48,43 @@ CLOSED ──(failures exceed threshold)──> OPEN
 - **FAIL** — Total number of failed calls (exceptions thrown by the protected 
code)
 - **RATE%** — Current failure rate percentage in the sliding window. When this 
exceeds the configured threshold, the circuit trips to OPEN
 - **REJECT** — Calls rejected because the circuit is OPEN. These calls never 
reach the downstream service — they fail fast with a fallback
+- **FALLBACK** — Calls answered by the `onFallback`, whatever the cause: a 
failed call, a timeout or a rejected call. This is how many callers got a 
degraded answer
+- **TIMEOUT** — Calls that hit the configured timeout. The breaker counts them 
as failures, so this tells a slow service apart from a broken one
 - **SINCE-LAST** — Time since the last circuit breaker activity, shown as up 
to two values separated by `/`: success/failed (e.g., `3s/1m14s`). Values are 
omitted when there is no activity of that type
 
 ## Example Screen
 
 ```
- ROUTE    ID            COMPONENT     STATE   WINDOW  INFLIGHT  SUCCESS  FAIL  
RATE%  REJECT
- route1   circuitBrk1   resilience4j  CLOSED  10      0         450      5     
1.0%   0
- route2   circuitBrk2   resilience4j  OPEN    10      0         100      8     
80.0%  25
+ ROUTE        ID                COMPONENT     STATE   WINDOW  INFLIGHT  
SUCCESS  FAIL  RATE%  REJECT  FALLBACK  TIMEOUT  SINCE-LAST
+ checkout     payment-breaker   resilience4j  CLOSED      10         1      
450     5     1%                 5        2  1s/3m12s
+ stock-check  supplier-breaker  resilience4j  OPEN         4         0        
3     2    50%       9       11           11s/9s
 ```
 
-In this example, `route1` is healthy with a 1% failure rate. `route2`
-has tripped open with an 80% failure rate — 25 calls have been rejected
-since it opened. The circuit will stay open until the wait timeout
-expires, then try a few test calls in HALF_OPEN state.
+Zero counts are shown blank, so a healthy breaker is a quiet line.
+
+`payment-breaker` is healthy with a 1% failure rate. Its five failures
+were all answered by the fallback, and two of them were timeouts: the
+payment provider is occasionally slow, not broken.
+
+`supplier-breaker` has tripped open: two real failures in a window of
+four is a 50% failure rate. Since it opened, 9 calls were rejected
+without calling the supplier, and the fallback answered all 11 callers.
+REJECT and FALLBACK together show what the breaker saved: the callers
+got an answer, and the supplier got a rest. The circuit stays open
+until the wait timeout expires, then lets a probe call through in
+HALF_OPEN state.
+
+## Reading the counters
+
+- FAIL counts calls the protected code failed, timeouts included.
+  TIMEOUT tells how many of those were timeouts.
+- REJECT counts calls the OPEN breaker refused. They are not in FAIL.
+- FALLBACK counts every call the `onFallback` answered, so it is
+  normally FAIL plus REJECT plus the bulkhead rejections, as long as
+  the breaker has a fallback. Without a fallback it stays at zero and
+  the exceptions reach the route instead.
+- Calls rejected by a full bulkhead are not in REJECT; they appear as
+  `bulkhead` in the detail view.
 
 ## Detail View
 
@@ -72,7 +95,8 @@ The bottom panel shows when a circuit breaker is selected:
 - **Sparkline chart**: Mirrored view showing successful calls (green,
   upward) vs failed calls (red, downward) over time
 - **Metrics**: Detailed counts for total, fail, inflight, reject,
-  and timing statistics (mean/min/max processing time)
+  fallback, timeout, bulkhead (calls rejected because the bulkhead
+  was full) and timing statistics (mean/min/max processing time)
 
 ## Configuration Tips
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTabRenderTest.java
index 402dafea9a67..203d787acc2a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTabRenderTest.java
@@ -206,6 +206,22 @@ class CircuitBreakerTabRenderTest {
 
     // ---- Helper methods ----
 
+    @Test
+    void renderShowsFallbackAndTimeoutColumns() {
+        CircuitBreakerInfo cb = addCircuitBreaker("route1", "cb1", 
"resilience4j", "open");
+        cb.notPermittedCalls = 25;
+        cb.fallbackCalls = 33;
+        cb.timedOutCalls = 2;
+
+        CircuitBreakerTab tab = new CircuitBreakerTab(ctx, new 
MetricsCollector());
+        String rendered = TuiTestHelper.renderToString(tab, 180, 30);
+
+        assertTrue(rendered.contains("FALLBACK"), "Should show FALLBACK 
header");
+        assertTrue(rendered.contains("TIMEOUT"), "Should show TIMEOUT header");
+        assertTrue(rendered.contains("33"), "Should render the fallback 
count");
+        assertTrue(rendered.contains("25"), "Should render the reject count");
+    }
+
     private CircuitBreakerInfo addCircuitBreaker(String routeId, String id, 
String component, String state) {
         CircuitBreakerInfo cb = new CircuitBreakerInfo();
         cb.routeId = routeId;

Reply via email to