Getting Started
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page takes a Spring Boot service from nothing to a working circuit breaker: the dependencies, one annotated method with a fallback, the YAML that configures it, and the Actuator endpoint that shows its state. It was written against Resilience4j 2.4.0 (released 14 March 2026), Spring Boot 4.1.1, Spring Framework 7.0.x and Java 21, and every snippet on it was built and run on exactly those versions on 4 October 2026. A Spring Boot 3.5.x variant (verified on 3.5.16 and Spring Framework 6.2.x) follows each Boot 4 step. If the vocabulary is new, read Resilience Fundamentals first.
The modules
Resilience4j is a set of small artifacts under the group io.github.resilience4j. A Spring Boot application normally
needs only the starter, which brings the core modules with it, but it helps to know what the rest are.
| Artifact | What it provides | Used in this section |
|---|---|---|
|
The shared base: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The |
|
|
Reactor operators ( |
|
|
The same operators for RxJava 2 and 3. |
|
|
Coroutine-friendly extensions ( |
|
|
Decorators for OpenFeign clients ( |
|
|
Binds every module’s metrics to Micrometer and, with it, Prometheus. |
|
|
The framework-level Spring integration: the AspectJ aspects, the annotations' behaviour, registries as beans, the property binding model. |
|
|
The Spring Boot 3 starter: auto-configuration over |
This page (Boot 3 variant) |
|
The Spring Boot 4 starter, the same shape for Boot 4 / Framework 7. |
This page |
|
A bill of materials with the versions of the modules above. |
This page |
|
Builds registries from Apache Commons Configuration (properties or YAML) in applications without Spring. |
Behind these sit a few supporting artifacts you rarely add yourself: resilience4j-annotations (the annotation
types), -consumer (CircularEventConsumer, the event buffer), -circularbuffer, -framework-common (the
property classes shared by the Spring modules) and -metrics (Dropwizard Metrics).
Two things have changed on the module map. resilience4j-spring-boot2 and resilience4j-spring-cloud2 were last
published in 2.3.0. Their directories remain in the repository but are excluded from settings.gradle, so they are not built from the 2.4.0 sources, and a Boot 2 application stays on 2.3.0. And
resilience4j-spring-boot4 is new: it is the starter for Spring Boot 4 and Spring Framework 7.
Source: Resilience4j: Getting Started and the 2.4.0
settings.gradle (the list of modules
that are built), checked against Maven Central
(io.github.resilience4j).
Dependencies for Spring Boot 4
A Spring Boot 4 service needs four things: the Resilience4j starter, spring-boot-starter-aspectj (so the
annotations work), spring-boot-starter-actuator (for the endpoints and metrics), and a web starter. Three details
are specific to Boot 4 and 2.4.0, and each was confirmed by running a sample project.
-
The starter is not in the BOM.
resilience4j-bom2.4.0 lists the core modules,spring6,spring-boot3,reactor,micrometerand others, but notresilience4j-spring-boot4(norresilience4j-hedgeorresilience4j-commons-configuration). Import the BOM for the modules it covers, and give the starter an explicit version. Leaving the version out fails with'dependencies.dependency.version' … is missing. -
spring-boot-starter-aspectjis not transitive. The starter does not add AspectJ, and Spring Boot 4 retiredspring-boot-starter-aop(its last release is a 4.0.0 milestone). Withoutspring-boot-starter-aspectjthe application starts normally, the registries and Actuator endpoints exist, and@CircuitBreakersilently does nothing. -
Actuator is not transitive either. Add
spring-boot-starter-actuatoryourself.
pom.xml for a Spring Boot 4 service<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/>
</parent>
<properties>
<java.version>21</java.version>
<resilience4j.version>2.4.0</resilience4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bom</artifactId>
<version>${resilience4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot4</artifactId>
<version>${resilience4j.version}</version>
</dependency>
</dependencies>
spring-boot-starter-restclient is there for the Bookshelf code, not for Resilience4j: Spring Boot 4 split HTTP
client support out of the web starters, so the auto-configured RestClient.Builder bean used by the pricing client
below only exists when this starter is present (a reactive service adds spring-boot-starter-webclient for
WebClient.Builder).
plugins {
java
id("org.springframework.boot") version "4.1.1"
id("io.spring.dependency-management") version "1.1.7"
}
val resilience4jVersion = "2.4.0"
java {
toolchain { languageVersion.set(JavaLanguageVersion.of(21)) }
}
repositories { mavenCentral() }
dependencyManagement {
imports {
mavenBom("io.github.resilience4j:resilience4j-bom:$resilience4jVersion")
}
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-restclient")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-aspectj")
implementation("io.github.resilience4j:resilience4j-spring-boot4:$resilience4jVersion")
}
Source: Resilience4j: Spring Boot 2 & 3 for the Boot 3 form, which
has no Boot 4 equivalent on readme.io; the Boot 4 setup above is derived from the published
resilience4j-spring-boot4
POM and the v2.4.0 auto-configuration imports,
and was verified by building and running the Bookshelf service. The Maven build was executed. The Gradle
translation is a mechanical mapping of the same coordinates and was not run.
The starter pulls resilience4j-spring6 and resilience4j-micrometer, and through the latter the five core modules
(circuitbreaker, retry, ratelimiter, bulkhead, timelimiter). That is enough to code against CircuitBreakerRegistry
and the annotations. Add resilience4j-all when you want the Decorators builder, resilience4j-reactor for
Mono/Flux operators, and list any core module explicitly if you depend on it directly, rather than relying on
what happens to arrive transitively.
The starter checks your Boot version
Both starters ship a version verifier that fails the application at startup when the Spring Boot major version is
wrong. Putting resilience4j-spring-boot3 on a Boot 4 application stops it with
Module 'io.github.resilience4j:resilience4j-spring-boot3' is only compatible with Spring Boot 3.x, and the Boot 4
starter reports the mirror message on Boot 3. A mismatch therefore cannot go unnoticed, which is useful during
migrations.
Dependencies for Spring Boot 3
On Spring Boot 3.5.x the starter is resilience4j-spring-boot3, AspectJ comes from spring-boot-starter-aop, and
spring-boot-starter-web already includes RestClient. This time the BOM does manage the starter, so no explicit
version is needed.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bom</artifactId>
<version>2.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
</dependencies>
plugins {
java
id("org.springframework.boot") version "3.5.16"
id("io.spring.dependency-management") version "1.1.7"
}
dependencyManagement {
imports { mavenBom("io.github.resilience4j:resilience4j-bom:2.4.0") }
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-aop")
implementation("io.github.resilience4j:resilience4j-spring-boot3")
}
Source: Resilience4j: Spring Boot 2 & 3. The Boot 3 differences, in one place:
| Aspect | Spring Boot 4.1 / Framework 7 | Spring Boot 3.5 / Framework 6.2 |
|---|---|---|
Starter |
|
|
AspectJ for annotations |
|
|
HTTP client builders |
|
Included in |
Health API package |
|
|
Package of annotations and Java API |
Unchanged: |
Unchanged |
Everything else on this page, including the YAML and the Actuator output, is identical on both lines.
AspectJ is optional, and what you lose without it
The annotations (@CircuitBreaker, @Retry, @RateLimiter, @Bulkhead, @TimeLimiter) are implemented as
Spring AOP aspects, and they need the AspectJ weaver
on the classpath to be recognised. Without spring-boot-starter-aspectj (Boot 4) or spring-boot-starter-aop (Boot 3):
-
the auto-configuration still creates the registries (
CircuitBreakerRegistry,RetryRegistry, and so on), the Actuator endpoints and the Micrometer metrics; -
no aspect beans are created, annotated beans are not proxied, and the annotations have no effect and raise no error.
That last point is the common first-hour mistake: the fallback never runs and the breaker never opens, and nothing in the log says why. The remedy is the missing dependency. A service that wants only programmatic use can leave AspectJ out deliberately and inject the registry:
@Component
public class ProgrammaticPricing {
private final RestClient http;
private final CircuitBreaker breaker;
public ProgrammaticPricing(RestClient.Builder builder,
CircuitBreakerRegistry registry,
@Value("${bookshelf.pricing.url:http://localhost:8089}") String baseUrl) {
this.http = builder.baseUrl(baseUrl).build();
this.breaker = registry.circuitBreaker("pricing"); // configured from application.yml
}
public Money price(String isbn) {
return breaker.executeSupplier(
() -> http.get().uri("/prices/{isbn}", isbn).retrieve().body(Money.class));
}
}
Source: Resilience4j: Spring Boot 2 & 3 and the
Spring Boot AOP reference. The programmatic style,
with RestClient, WebClient and HttpClient examples, is covered in
Programmatic Usage.
A first circuit breaker: the pricing call
The Bookshelf bookshelf-service asks the slow and flaky pricing-service for a book’s price. When that service
is failing, the call should stop being attempted after a few failures and the page should show the last known price
instead. One annotation and one fallback method do it.
@CircuitBreaker with a fallbackpackage com.example.bookshelf;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@Component
public class PricingClient {
private final RestClient http;
public PricingClient(RestClient.Builder builder,
@Value("${bookshelf.pricing.url:http://localhost:8089}") String baseUrl) {
this.http = builder.baseUrl(baseUrl).build();
}
@CircuitBreaker(name = "pricing", fallbackMethod = "lastKnownPrice")
public Money price(String isbn) {
return http.get().uri("/prices/{isbn}", isbn).retrieve().body(Money.class);
}
Money lastKnownPrice(String isbn, Throwable t) {
return new Money(isbn, 0, true);
}
}
public record Money(String isbn, long cents, boolean stale) {}
Three things to notice:
-
name = "pricing"selects the instance. It is the same name used in the YAML below and in the Actuator output. One instance per remote backend is the rule (see Registries, Configuration & Decorators). -
The fallback has the same parameters as the protected method plus a trailing
Throwable, and the same return type. It runs when the call throws, and also when the breaker is open and rejects the call withCallNotPermittedException. The full resolution rules are in Fallbacks & Composition. -
The fallback marks its answer as
stale, so the caller can tell a degraded price from a live one.
A controller exposes it:
@RestController
public class BookshelfController {
private final PricingClient pricing;
public BookshelfController(PricingClient pricing) {
this.pricing = pricing;
}
@GetMapping("/books/{isbn}/price")
public Money price(@PathVariable String isbn) {
return pricing.price(isbn);
}
}
Source: Resilience4j: Spring Boot 2 & 3 (the @CircuitBreaker
annotation and fallbackMethod) and Resilience4j: CircuitBreaker.
The configuration
The instance is configured under resilience4j.circuitbreaker.instances.<name> in application.yml. Every key has a
default; only the ones that matter for this example are set. The full list, with defaults, is in
CircuitBreaker.
application.yml: the pricing breaker and the Actuator exposureresilience4j:
circuitbreaker:
instances:
pricing:
slidingWindowType: COUNT_BASED
slidingWindowSize: 10
minimumNumberOfCalls: 5
failureRateThreshold: 50
waitDurationInOpenState: 10s
permittedNumberOfCallsInHalfOpenState: 3
registerHealthIndicator: true
management:
endpoints:
web:
exposure:
include: health,circuitbreakers
endpoint:
health:
show-details: always
health:
circuitbreakers:
enabled: true
Read the settings as a sentence: judge the last 10 calls; once at least 5 have been recorded and 50 per cent or more
failed, open the circuit for 10 seconds; then let 3 trial calls through to decide whether to close it again.
registerHealthIndicator and management.health.circuitbreakers.enabled are both off by default and both needed for
the breaker to appear in /actuator/health. Exposing show-details: always and circuitbreakers is fine on a laptop;
on a real service, expose Actuator on a management port and secure it, as described in
Observability.
Source: Resilience4j: Spring Boot 2 & 3 (the property prefix and the instance model) and Spring Boot: Actuator endpoints (exposure).
Running it
Start the application with ./mvnw spring-boot:run (or ./gradlew bootRun). To see the breaker work you need a
failing pricing-service; the quickest way is to start nothing on port 8089, so every call is refused. Call the
endpoint six times:
for i in 1 2 3 4 5 6; do
curl -s localhost:8080/books/978-0-13-468599-1/price; echo
done
{"isbn":"978-0-13-468599-1","cents":0,"stale":true}
{"isbn":"978-0-13-468599-1","cents":0,"stale":true}
{"isbn":"978-0-13-468599-1","cents":0,"stale":true}
{"isbn":"978-0-13-468599-1","cents":0,"stale":true}
{"isbn":"978-0-13-468599-1","cents":0,"stale":true}
{"isbn":"978-0-13-468599-1","cents":0,"stale":true}
Every answer is the fallback, but the first five reached the network and failed, while the sixth was rejected by the breaker without a connection attempt. The endpoint shows which:
curl -s localhost:8080/actuator/circuitbreakers
{
"circuitBreakers": {
"pricing": {
"bufferedCalls": 5,
"failedCalls": 5,
"failureRate": "100.0%",
"failureRateThreshold": "50.0%",
"notPermittedCalls": 1,
"slowCallRate": "0.0%",
"slowCallRateThreshold": "100.0%",
"slowCalls": 0,
"slowFailedCalls": 0,
"state": "OPEN"
}
}
}
bufferedCalls and failedCalls count the five recorded attempts, state is OPEN, and notPermittedCalls is 1:
the sixth request. After ten seconds the breaker moves to HALF_OPEN on the next call, lets up to three trial calls
through, and closes again if they succeed.
With the health indicator enabled, the same breaker appears in /actuator/health:
curl -s localhost:8080/actuator/health/circuitBreakers
{
"details": {
"pricing": {
"details": {
"failureRate": "100.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "0.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 5,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 5,
"notPermittedCalls": 1,
"state": "OPEN"
},
"status": "CIRCUIT_OPEN"
}
},
"status": "UNKNOWN"
}
An open breaker reports the custom status CIRCUIT_OPEN, and the aggregate for the circuitBreakers component is
UNKNOWN, so by default an open breaker does not take the application’s overall health DOWN. That is deliberate:
an unhealthy dependency is not a reason for Kubernetes to restart this service. How to change it per instance
(allowHealthIndicatorToFail) and which probes to keep it out of are covered in
Observability.
A test for the same behaviour, using WireMock to play the pricing service, is in Testing Resilience.
Source: Resilience4j: Spring Boot 2 & 3 (the Actuator endpoints and health indicator) and Spring Boot: Actuator endpoints. The JSON above is the real output of Resilience4j 2.4.0 on Spring Boot 4.1.1.
What the annotation does
The request does not call PricingClient directly. Spring wraps the bean in a proxy (a CGLIB subclass whose class name
starts with PricingClient and contains SpringCGLIB in the debugger), and the Resilience4j aspect is part of that proxy’s interceptor
chain. When /books/{isbn}/price is called, the aspect looks up the pricing breaker in the registry, asks it for
permission, runs the real method, records the outcome, and calls the fallback if the call failed or was rejected.
Because the aspect works through the proxy, it only sees calls that enter the bean from outside. A method calling
another method on this bypasses it, and private or final methods cannot be intercepted. These and other
proxy pitfalls are collected in
Annotations & Aspect Order.
The official demo
Resilience4j publishes a runnable demo for the Boot 3 starter, resilience4j-spring-boot3-demo, and an older one for Boot 2. They are useful for seeing the annotations and Actuator endpoints in a complete project. As of 4 October 2026 there is no Spring Boot 4 demo in the Resilience4j organisation, and the Boot 3 demo has not been updated since January 2025, so treat it as a source of ideas rather than a template for Boot 4 versions and dependencies. The code on this page is the Boot 4 reference.
Source: resilience4j-spring-boot3-demo on GitHub.
Common first-hour problems
| Symptom | Likely cause | Fix |
|---|---|---|
The annotation has no effect: the fallback never runs, the breaker never opens |
No AspectJ on the classpath (Boot 4: |
Add the starter; call the bean from another bean |
|
The 2.4.0 BOM does not manage the Boot 4 starter |
Give the starter an explicit version |
|
The starter’s major version does not match the Boot version |
Use |
|
HTTP client support moved out of the web starter |
Add |
|
The endpoint is not exposed |
Add |
The breaker is missing from |
|
Set both to |
The YAML key is silently ignored |
A typo, or a property that exists only on the builder (for example |
Check the key against the property tables in Spring Boot Configuration |
Where to go next
-
Registries, Configuration & Decorators: what the registry bean is and how to build instances and decorators by hand.
-
CircuitBreaker: every property, the state machine and tuning.
-
Spring Boot Configuration: the YAML structure for all modules.
-
Metrics and observability: the Actuator and Micrometer basics the resilience endpoints build on.