Files
spring-boot-demo/resilience4j-circuit-breaker/docs/01-two-resilience-stacks.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

94 lines
5.0 KiB
Markdown

# 1. Two resilience stacks on one classpath
[README](../README.md) | Next: [02-circuit-breaker.md](02-circuit-breaker.md)
Spring Boot 4.1 sits on Spring Framework 7, and Framework 7 shipped something new under
`org.springframework.resilience`: `@Retryable`, `@ConcurrencyLimit`, and the annotation that
turns them on, `@EnableResilientMethods`. None of this existed in Framework 6. This repo pins
down exactly what moved into core, what didn't, and two build breaks you will hit the moment
you try to wire it up on Boot 4 — both found by running the build, not by reading a changelog.
## What's actually in core (verified by `javap`, not by the reference docs)
`org.springframework.resilience.annotation.Retryable` (an annotation, not the unrelated
`org.springframework.core.retry.Retryable` *interface* that also ships in `spring-core` — two
classes with the same simple name in the same major version, easy to import the wrong one):
```
value(), includes(), excludes(), predicate(),
maxRetries(), maxRetriesString(),
timeout(), timeoutString(),
delay(), delayString(),
jitter(), jitterString(),
multiplier(), multiplierString(),
maxDelay(), maxDelayString(),
timeUnit()
```
`org.springframework.resilience.annotation.ConcurrencyLimit`:
```
value(), limit(), limitString(), policy() // policy: BLOCK (default) or REJECT
```
Neither annotation has a `fallbackMethod` attribute. Retrying exhausted just rethrows.
Rejecting under `REJECT` throws `org.springframework.resilience.InvocationRejectedException`
(a `java.util.concurrent.RejectedExecutionException` subtype) straight at the caller.
There is no circuit breaker class anywhere in `spring-context-7.0.9.jar` (grepped the whole
jar listing for `circuitbreaker`, `ratelimit`, `bulkhead` — zero matches). `@ConcurrencyLimit`
is the closest core has to a Resilience4j Bulkhead, and even that is a decades-old class
repurposed: `ConcurrencyLimitBeanPostProcessor$ResilienceConcurrencyThrottleInterceptor`
extends `org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor`, which has shipped
in Spring since the 1.x era.
## `@EnableResilientMethods` is not automatic
Spring Boot 4.1's autoconfigure jar carries no auto-configuration for the resilience package —
grepping `spring-boot-autoconfigure-4.1.1.jar`'s listing for "resilien" returns nothing. You
must put `@EnableResilientMethods` on a `@Configuration` class yourself (this repo puts it on
the `@SpringBootApplication` class). Skip it and the annotations are inert: no error, no log
line, the method just runs unprotected.
## Build break #1: `spring-boot-starter-aop` no longer exists
Every pre-Boot-4 Resilience4j guide, including the version of this post it replaces, tells you
to add `spring-boot-starter-aop`. On Boot 4 that dependency breaks the build outright:
```
[ERROR] 'dependencies.dependency.version' for org.springframework.boot:spring-boot-starter-aop:jar is missing.
```
`repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-aop/maven-metadata.xml`
confirms it: the last version ever published is `4.0.0-M2`, a milestone. It never reached
Boot 4.0 GA and was not revived for 4.1. The fix is `org.springframework:spring-aop` directly —
see [pom.xml](../pom.xml).
## Build break #2 (silent): no `aspectjweaver`, no proxy, no error
Swapping in `spring-aop` alone gets you a clean build and a **wrong result**. Resilience4j's
Spring integration (`resilience4j-spring6`, which is what `resilience4j-spring-boot4` actually
depends on — see below) implements `CircuitBreakerAspect`, `BulkheadAspect`, `RateLimiterAspect`
and friends as real `@Aspect` classes (confirmed with `unzip -l` on the jar). Spring's
`AnnotationAwareAspectJAutoProxyCreator` needs `org.aspectj:aspectjweaver` on the classpath to
even recognise a bean as an aspect. Without it, **zero proxies get created, for anything**
Resilience4j's annotations and Spring's own `@Retryable` both go completely inert, silently.
That is exactly what happened building this repo: `CircuitBreakerTripAndRecoverTest` failed
with the raw `DownstreamUnavailableException` propagating straight out of `R4jPaymentService`,
no fallback, no state tracking — because there was no proxy in front of it at all. Adding
`org.aspectj:aspectjweaver` fixed every failing test in the same run. `spring-boot-starter-aop`
used to bundle this for you; its replacement doesn't, and nothing tells you that.
## A naming trap in the dependency itself
`resilience4j-spring-boot4` is a real, separate artifact from `resilience4j-spring-boot3`
(both currently at `2.4.0`) — but its own POM depends on `resilience4j-spring6`, `spring-core
7.0.2`, `spring-context 7.0.2` and `spring-boot-autoconfigure 4.0.0`. The "spring-boot4" in the
artifact name is about which *Boot* generation it targets, not which internal Resilience4j
module version it's built on — that module never got renamed to "spring7". If you're grepping
your dependency tree for "spring7" expecting to find the pieces Boot 4.1 pulls in, you won't.
Next: [02-circuit-breaker.md](02-circuit-breaker.md) — the Resilience4j side, verified against
a real trip/recover run.