Circuit Breaker with Resilience4j in Spring Boot: Timeout, Retry, Fallback and Metrics

Production Resilience4j circuit-breaker configuration for Spring Boot, including timeout, retry ordering, slow-call thresholds, fallbacks, metrics and failure classification.

Romharshan Singh
Romharshan SinghSenior Solution Architect • AI & Cloud Mentor
1 September 20264 min read1 viewsUpdated 1 Sept 2026
Circuit Breaker with Resilience4j in Spring Boot: Timeout, Retry, Fallback and Metrics

Circuit Breaker with Resilience4j in Spring Boot: Timeout, Retry, Fallback and Metrics

Where I use circuit breakers

External supplier, payment and downstream microservice calls can become slow long before they become completely unavailable.

If every request waits:

plaintext
thread
connection
memory
request slot

remain occupied.

The circuit breaker protects the caller by failing fast when evidence says the dependency is unhealthy.

State model

plaintext
CLOSED
  |
failure/slow-call threshold
  v
OPEN
  |
waitDuration
  v
HALF_OPEN
  |
controlled probe calls
  +-- success -> CLOSED
  `-- failure -> OPEN

Resilience4j configuration

yaml
resilience4j:
  circuitbreaker:
    instances:
      hotelSupplier:
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 20
        minimumNumberOfCalls: 10
        failureRateThreshold: 50
        slowCallRateThreshold: 50
        slowCallDurationThreshold: 2s
        waitDurationInOpenState: 20s
        permittedNumberOfCallsInHalfOpenState: 3
        automaticTransitionFromOpenToHalfOpenEnabled: true

  retry:
    instances:
      hotelSupplier:
        maxAttempts: 2
        waitDuration: 150ms
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2

  timelimiter:
    instances:
      hotelSupplier:
        timeoutDuration: 2500ms

I treat a slow call as a failure signal because an HTTP 200 after twenty seconds can still destroy the upstream SLA.

Spring service

java
@Service
public class HotelAvailabilityService {

    private final HotelSupplierClient client;

    public HotelAvailabilityService(
        HotelSupplierClient client
    ) {
        this.client = client;
    }

    @CircuitBreaker(
        name = "hotelSupplier",
        fallbackMethod = "fallback"
    )
    @Retry(name = "hotelSupplier")
    public HotelAvailability search(
        HotelSearchRequest request
    ) {
        return client.search(request);
    }

    public HotelAvailability fallback(
        HotelSearchRequest request,
        Throwable error
    ) {
        return HotelAvailability.degraded(
            request.hotelId(),
            "LIVE_AVAILABILITY_UNAVAILABLE"
        );
    }
}

A fallback must be business-valid

For hotel search, returning a degraded state or cached non-bookable content can be acceptable.

For payment capture, this would be dangerous:

plaintext
payment failed
fallback -> "SUCCESS"

A fallback is not a mechanism for hiding failures.

Possible fallback choices:

  • cached result;
  • partial data;
  • queue for later;
  • alternate provider;
  • explicit unavailable response.

Exception classification

Business errors such as:

plaintext
invalid date
supplier rejects unsupported room type
authentication denied

should not necessarily count against circuit health.

I configure recorded/ignored exceptions so the breaker represents dependency health rather than user mistakes.

Retry and circuit breaker ordering

I do not want:

plaintext
one call
 -> retry 5 times
 -> breaker sees one final failure

without understanding the load effect.

Nor do I want a breaker to count every local validation failure.

Decorator order and metrics need to be tested under load.

My usual sequence is conceptually:

plaintext
overall deadline
  |
bulkhead/concurrency
  |
circuit breaker
  |
small retry policy
  |
HTTP timeout

The exact Resilience4j annotation/decorator order depends on implementation, so I verify it with metrics rather than assuming.

Metrics

I expose:

plaintext
state
failure rate
slow-call rate
buffered calls
not-permitted calls
successful/failed calls
retry attempts
timeout count
fallback count

Prometheus examples usually come from Resilience4j Micrometer integration.

Operationally, not_permitted_calls is important: it tells me the breaker is actively shedding requests.

Half-open traffic

A half-open state should permit only a few test calls.

If 500 pods all send unrestricted probes at the same time, the recovering dependency can be knocked over again.

Failure scenarios

Breaker opens too aggressively

Increase minimum calls/window based on traffic shape. A service receiving two calls per hour needs a different policy from 10,000 calls/sec.

Fallback returns stale sensitive data

Do not cache/fallback where freshness is legally/business-critical.

Breaker never opens on slow calls

Track slow-call rate, not only exceptions.

Every instance has local breaker state

That is normally acceptable. Sharing breaker state centrally introduces another dependency. The load balancer spreads observations across instances, so thresholds should be tested at instance level.

Production checklist

  • Timeout first.
  • Count slow calls.
  • Ignore business/validation errors.
  • Keep retry attempts low.
  • Use jitter for retry delays.
  • Define valid fallback semantics.
  • Export breaker metrics.
  • Alert on OPEN duration and rejected calls.
  • Test HALF_OPEN under load.
  • Pair with bulkhead/concurrency control.

FAQ

Does circuit breaker replace timeout?

No. Timeout bounds one request. Circuit breaker uses history to stop sending requests.

Does it replace retry?

No. Retry handles a few transient failures. Circuit breaker protects against persistent failure.

Should every HTTP call have a breaker?

No. Use it where dependency failure can create cascading resource pressure or fast failure has business value.

- [Microservices Design Patterns I Have Used in Real Enterprise Projects](/articles/microservices-design-patterns-real-enterprise-projects) - [Circuit Breaker in Node/NestJS with Opossum](/articles/circuit-breaker-nodejs-nestjs-opossum)
  • Retry Pattern

  • Bulkhead Pattern

    Architect's final take

    The value of Resilience4j is not the annotation. The value is making timeout, retry, slow-call behavior and fallback measurable and deliberately bounded.

Was this article useful?

Your feedback helps prioritize deeper technical content.

Romharshan Singh
ABOUT THE AUTHOR

Romharshan Singh

Senior Solution Architect and Full Stack Technology Leader with 20+ years of enterprise engineering experience across AI, cloud, distributed systems, Java, Node.js, React, Angular, Kafka and Kubernetes.