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

resilience4j-core

The shared base: IntervalFunction, EventPublisher, RegistryStore, registries' common types and the functional interfaces (CheckedSupplier and friends).

Registries, Configuration & Decorators

resilience4j-circuitbreaker

CircuitBreaker, CircuitBreakerConfig, CircuitBreakerRegistry.

CircuitBreaker

resilience4j-retry

Retry, RetryConfig, RetryRegistry, synchronous and asynchronous.

Retry

resilience4j-ratelimiter

RateLimiter, RateLimiterConfig, RateLimiterRegistry.

RateLimiter

resilience4j-bulkhead

Bulkhead (semaphore) and ThreadPoolBulkhead, with their configs and registries.

Bulkhead

resilience4j-timelimiter

TimeLimiter, which bounds a Future or CompletionStage.

TimeLimiter

resilience4j-cache

Cache, a decorator over a JCache (JSR-107) cache.

Cache & Hedge

resilience4j-hedge

Hedge, speculative second requests for tail latency. Absent from the readme.io user guide.

Cache & Hedge

resilience4j-all

The Decorators builder, plus the circuitbreaker, ratelimiter, bulkhead, retry, cache, timelimiter and micrometer modules. Does not include hedge.

Registries, Configuration & Decorators

resilience4j-reactor

Reactor operators (transformDeferred) for Mono and Flux.

Reactive, Kotlin & RxJava

resilience4j-rxjava2, resilience4j-rxjava3

The same operators for RxJava 2 and 3.

Reactive, Kotlin & RxJava

resilience4j-kotlin

Coroutine-friendly extensions (suspend functions, Flow).

Reactive, Kotlin & RxJava

resilience4j-feign

Decorators for OpenFeign clients (Resilience4jFeign).

Gateway & OpenFeign

resilience4j-micrometer

Binds every module’s metrics to Micrometer and, with it, Prometheus.

Observability

resilience4j-spring6

The framework-level Spring integration: the AspectJ aspects, the annotations' behaviour, registries as beans, the property binding model.

Annotations & Aspect Order

resilience4j-spring-boot3

The Spring Boot 3 starter: auto-configuration over resilience4j-spring6, and the Actuator endpoints.

This page (Boot 3 variant)

resilience4j-spring-boot4

The Spring Boot 4 starter, the same shape for Boot 4 / Framework 7.

This page

resilience4j-bom

A bill of materials with the versions of the modules above.

This page

resilience4j-commons-configuration

Builds registries from Apache Commons Configuration (properties or YAML) in applications without Spring.

Registries, Configuration & Decorators

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-bom 2.4.0 lists the core modules, spring6, spring-boot3, reactor, micrometer and others, but not resilience4j-spring-boot4 (nor resilience4j-hedge or resilience4j-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-aspectj is not transitive. The starter does not add AspectJ, and Spring Boot 4 retired spring-boot-starter-aop (its last release is a 4.0.0 milestone). Without spring-boot-starter-aspectj the application starts normally, the registries and Actuator endpoints exist, and @CircuitBreaker silently does nothing.

  • Actuator is not transitive either. Add spring-boot-starter-actuator yourself.

Maven: 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).

Gradle (Kotlin DSL): the same dependencies
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.

Maven: Spring Boot 3.5.x
<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>
Gradle (Kotlin DSL): Spring Boot 3.5.x
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

resilience4j-spring-boot4, explicit version (not in the BOM)

resilience4j-spring-boot3, version managed by the BOM

AspectJ for annotations

spring-boot-starter-aspectj

spring-boot-starter-aop

HTTP client builders

spring-boot-starter-restclient / -webclient supply RestClient.Builder / WebClient.Builder

Included in spring-boot-starter-web / -webflux

Health API package

org.springframework.boot.health.contributor

org.springframework.boot.actuate.health

Package of annotations and Java API

Unchanged: io.github.resilience4j.*

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:

Programmatic use without annotations: the registry is an injectable bean
@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.

The pricing client: @CircuitBreaker with a fallback
package 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 with CallNotPermittedException. 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:

The controller needs no resilience code
@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 exposure
resilience4j:
  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:

Six calls against a pricing service that is down
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:

The circuit breakers endpoint
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.

sequenceDiagram participant C as BookshelfController participant P as PricingClient proxy participant A as CircuitBreakerAspect participant R as CircuitBreakerRegistry participant B as CircuitBreaker pricing participant T as PricingClient target participant S as pricing-service C->>P: price(isbn) P->>A: intercept @CircuitBreaker A->>R: circuitBreaker("pricing") R-->>A: instance (config from application.yml) A->>B: tryAcquirePermission() alt circuit CLOSED or HALF_OPEN B-->>A: permitted A->>T: proceed T->>S: GET /prices/isbn alt call succeeds S-->>T: 200 price T-->>A: Money A->>B: onSuccess(duration) else call fails S--xT: error or timeout T-->>A: exception A->>B: onError(duration, exception) A->>T: lastKnownPrice(isbn, exception) end else circuit OPEN B-->>A: CallNotPermittedException A->>T: lastKnownPrice(isbn, exception) end A-->>P: result P-->>C: Money
Figure 1. A request through the aspect proxy

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: spring-boot-starter-aspectj; Boot 3: spring-boot-starter-aop), or the call is a self-invocation

Add the starter; call the bean from another bean

dependencies.dependency.version …​ is missing for resilience4j-spring-boot4

The 2.4.0 BOM does not manage the Boot 4 starter

Give the starter an explicit version

only compatible with Spring Boot 3.x (or 4.x) at startup

The starter’s major version does not match the Boot version

Use -spring-boot4 on Boot 4 and -spring-boot3 on Boot 3

No qualifying bean of type RestClient.Builder on Boot 4

HTTP client support moved out of the web starter

Add spring-boot-starter-restclient (and -webclient for WebClient)

/actuator/circuitbreakers returns 404

The endpoint is not exposed

Add circuitbreakers to management.endpoints.web.exposure.include

The breaker is missing from /actuator/health

registerHealthIndicator and management.health.circuitbreakers.enabled both default to off

Set both to true

The YAML key is silently ignored

A typo, or a property that exists only on the builder (for example ignoreExceptionsPrecedenceEnabled)

Check the key against the property tables in Spring Boot Configuration

Where to go next