# resilience4j-circuit-breaker Companion module for the ankurm.com post [**"Resilience4j Circuit Breaker in Spring Boot 4.1: What It's Still For"**](https://ankurm.com/resilience4j-circuit-breaker-spring-boot/). Every claim in the post about the new `@Retryable`/`@ConcurrencyLimit` API in Spring Framework 7, and about how it does and doesn't overlap with Resilience4j, is backed by a file under `docs/output/` produced by a real test run against real Maven Central artifacts. Lives in this container repo (not as its own top-level repository) alongside [`../resilience`](../resilience), the companion module for the sibling post on Framework 7's built-in `@Retryable`/`@ConcurrencyLimit`. The two modules overlap in theme by design — this one is entered from "I want a circuit breaker," that one from "what did Framework 7 just ship" — and intentionally keep separate demo apps rather than sharing one, since each post's transcripts need to stay independently reproducible from its own module. ## Versions (verified against `repo1.maven.org` maven-metadata.xml, not aggregators) | Component | Version | Notes | |---|---|---| | JDK | 25 (Temurin 25.0.4.1+1) | latest LTS | | Spring Boot | 4.1.1 | latest GA at time of writing; 4.2.0-M1 exists but is a milestone | | Spring Framework | 7.0.9 | latest GA; 7.1.0-M1 exists but is a milestone | | Resilience4j | 2.4.0 (`resilience4j-spring-boot4`) | depends internally on `resilience4j-spring6`, not a "spring7" module — see [docs/01](docs/01-two-resilience-stacks.md) | ## Quickstart ```bash ./scripts/run-all.sh # regenerates every file in docs/output/ from a real test run ./scripts/run.sh # starts the app on :8080 to poke at by hand curl localhost:8080/actuator/health curl localhost:8080/actuator/circuitbreakers ``` Requires JDK 25 and Maven. First run must be online (Maven needs to fetch plugins into the local cache); `-o` works for subsequent builds. ## What's demonstrated where | Area | Source | Test | Transcript | |---|---|---|---| | Resilience4j circuit breaker: trip, stay open, half-open, recover | [`R4jPaymentService`](src/main/java/com/ankurm/resilience/r4j/R4jPaymentService.java) | [`CircuitBreakerTripAndRecoverTest`](src/test/java/com/ankurm/resilience/r4j/CircuitBreakerTripAndRecoverTest.java) | [`01`](docs/output/01-circuitbreaker-trip.txt) | | `@Retryable`: recovers from transient failure | [`SpringRetryablePaymentService`](src/main/java/com/ankurm/resilience/springresilience/SpringRetryablePaymentService.java) | [`SpringRetryableTest`](src/test/java/com/ankurm/resilience/springresilience/SpringRetryableTest.java) | [`03a`](docs/output/03a-retryable-recovers.txt) | | `@Retryable`: no memory between calls | same | same | [`03b`](docs/output/03b-retryable-no-memory.txt) | | `@Retryable`: self-invocation trap | same | same | [`03c`](docs/output/03c-retryable-self-invocation.txt) | | Resilience4j `@Retry` side-by-side (`maxAttempts` counts differently than `maxRetries`) | [`R4jRetryService`](src/main/java/com/ankurm/resilience/r4j/R4jRetryService.java) | [`R4jRetryTest`](src/test/java/com/ankurm/resilience/r4j/R4jRetryTest.java) | [`03d`](docs/output/03d-r4j-retry.txt), [`03e`](docs/output/03e-r4j-retry-exhaustion.txt) | | `@ConcurrencyLimit` BLOCK policy | [`ConcurrencyLimitedService`](src/main/java/com/ankurm/resilience/springresilience/ConcurrencyLimitedService.java) | [`ConcurrencyLimitTest`](src/test/java/com/ankurm/resilience/springresilience/ConcurrencyLimitTest.java) | [`04a`](docs/output/04a-concurrencylimit-block.txt) | | `@ConcurrencyLimit` REJECT policy | same | same | [`04b`](docs/output/04b-concurrencylimit-reject.txt) | | Resilience4j `@Bulkhead` (bounded wait) for comparison | [`R4jBulkheadService`](src/main/java/com/ankurm/resilience/r4j/R4jBulkheadService.java) | same | [`04c`](docs/output/04c-r4j-bulkhead-comparison.txt) | | Real `/actuator/health` + `/actuator/circuitbreakers` with a breaker OPEN | [`R4jPaymentService`](src/main/java/com/ankurm/resilience/r4j/R4jPaymentService.java) | [`ActuatorHealthTest`](src/test/java/com/ankurm/resilience/r4j/ActuatorHealthTest.java) | [`05`](docs/output/05-actuator-health-tripped.txt) | ## Endpoints (from `scripts/run.sh`) | Endpoint | Purpose | |---|---| | `GET /actuator/health` | includes circuit breaker health when `management.health.circuitbreakers.enabled=true` | | `GET /actuator/circuitbreakers` | live circuit breaker state | | `GET /actuator/circuitbreakerevents` | event stream of state transitions | | `GET /actuator/metrics` | includes `resilience4j.circuitbreaker.*` Micrometer series | There is no custom diagnostic endpoint in this repo — the standard Actuator set above already exposes everything the post needed, so nothing has to be deleted before shipping. **Note on `/actuator/health` in 2.4.0:** with `management.endpoint.health.show-details: always`, `/actuator/health` reports only an aggregate `"circuitBreakers":{"status":"UNKNOWN"}` — no per-instance breakdown. The per-breaker detail (`state`, `failureRate`, `bufferedCalls`, etc.) lives at `/actuator/circuitbreakers` instead. Real captured output of both, side by side, with the breaker actually OPEN: [docs/output/05-actuator-health-tripped.txt](docs/output/05-actuator-health-tripped.txt). ## Documentation chapters 1. [Two resilience stacks on one classpath](docs/01-two-resilience-stacks.md) — what moved into Spring Framework 7 core, what didn't, and two build breaks you'll hit getting there 2. [The circuit breaker, verified](docs/02-circuit-breaker.md) 3. [@Retryable: retries, but no memory](docs/03-spring-retryable.md) 4. [@ConcurrencyLimit vs Resilience4j's Bulkhead](docs/04-concurrency-limit.md) 5. [Production checklist: which one, for what](docs/05-production-checklist.md) ## License MIT — see [LICENSE](LICENSE).