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.
5.0 KiB
1. Two resilience stacks on one classpath
README | Next: 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.
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 — the Resilience4j side, verified against a real trip/recover run.