Files
spring-boot-demo/resilience4j-circuit-breaker/README.md
T
asmhatre 320733265f Add resilience4j-circuit-breaker: Resilience4j 2.4.0 vs Spring Framework 7 core, on Boot 4.1
Companion module for the rewritten post 'Resilience4j Circuit Breaker in Spring Boot 4.1:
What It's Still For', reworked around Framework 7 now shipping @Retryable/@ConcurrencyLimit
in core. Covers what's still Resilience4j's job (circuit breaker, rate limiter, bulkhead's
bounded wait, fallback methods, Actuator/Micrometer metrics), the off-by-one between
maxAttempts and maxRetries, and two Boot-4.1 build breaks: spring-boot-starter-aop no longer
exists (renamed to spring-boot-starter-aspectj, proven with Maven Central metadata and the
renamed starter's own POM -- see resilience/docs/08-starter-aop-renamed-to-starter-aspectj.md
in this same repo), and the resulting fix uses that renamed starter directly rather than
assembling spring-aop + aspectjweaver by hand. Kept as its own module rather than a new
top-level repository, alongside the existing resilience/ module for the sibling Framework-7
post.
2026-09-18 08:40:50 +00:00

81 lines
5.7 KiB
Markdown

# 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).