Circuit Breaker, Retry & Timeout Patterns

30 Jul 2026, Updated: 31 Jul 2026 7 min read
1
A Circuit Breaker, Retry, and Timeout are resilience patterns that help distributed systems handle failures gracefully.

Why Do We Need Timeout, Retry, and Circuit Breaker?

Consider an e-commerce application where placing an order requires calling a Payment Service.

Under normal conditions, the Payment Service responds within a few hundred milliseconds. However, network problems, temporary overload, or service failures can cause requests to become slow or fail completely.

If the Order Service waits indefinitely for every failed request, application threads remain blocked while waiting for responses.

As more requests arrive, the thread pool eventually becomes exhausted, increasing response times and potentially making the entire application unavailable.

A resilient application should instead fail fast when a request takes too long, retry only transient failures that are likely to succeed, and temporarily stop calling services that are consistently failing until they recover.

The Timeout, Retry, and Circuit Breaker patterns work together to achieve these goals, improving the reliability and availability of Spring Boot microservices.

Timeout Pattern

A Timeout defines the maximum time an application waits for a response from another service. Without a timeout, a slow service could block application threads indefinitely.
        Application
              |
              v
    Downstream Service
              |
              |
              |------ Waiting ------>
              |
           Timeout
              |
              v
        Fail Request
Suppose a Payment Service normally responds within 200 milliseconds. Waiting several minutes for a response provides little value because the request has already failed from the user's perspective.

Instead, the application can configure a timeout of two seconds. If no response arrives within this period, the request fails immediately. In Spring Boot, using Java's HTTP Client:
HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(2))
        .build();
For Spring WebClient:

WebClient.builder()
        .clientConnector(
                new ReactorClientHttpConnector(
                        HttpClient.create()
                                .responseTimeout(Duration.ofSeconds(2))
                )
        )
        .build();
Choosing an appropriate timeout depends on the expected response time and service-level objectives.

Retry Pattern

Not every failure is permanent. Temporary network interruptions, brief database failovers, or short-lived service restarts often succeed when retried after a small delay.

The Retry Pattern automatically repeats failed operations before reporting an error.
          Request
             |
             v
      Attempt 1 ---- Failed
             |
           Retry
             |
             v
      Attempt 2 ---- Failed
             |
           Retry
             |
             v
      Attempt 3 ---- Success
For example, if a Payment Service experiences a temporary network timeout, retrying the request after a short delay may succeed once the network stabilizes.

Using Resilience4j:
@Retry(name = "paymentService")
public PaymentResponse processPayment() {
    return paymentClient.process();
}
Configuration:
resilience4j:
  retry:
    instances:
      paymentService:
        maxAttempts: 3
        waitDuration: 500ms
This configuration attempts the operation up to three times, waiting 500 milliseconds between attempts.

Exponential Backoff

Retrying immediately after every failure may overload an already struggling service. Instead, many systems use Exponential Backoff, where each retry waits longer than the previous one.
Attempt 1
    |
 500 ms
    |
    v
Attempt 2
    |
 1 second
    |
    v
Attempt 3
    |
 2 seconds
Using Resilience4j:
@Retry(name = "paymentService")
public PaymentResponse processPayment() {
    return paymentClient.process();
}
Configuration:
resilience4j:
  retry:
    instances:
      paymentService:
        maxAttempts: 3
        waitDuration: 500ms
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2
In this example, the first retry waits for 500 milliseconds, the second waits for approximately 1 second, and the third waits for approximately 2 seconds.

When Should You Retry?

Retries are appropriate only for transient failures, such as temporary network errors, short service outages, or HTTP 503 Service Unavailable responses.

Retries should generally be avoided for client errors such as 400 Bad Request, 401 Unauthorized, or 404 Not Found, since repeating the request will not change the outcome.

Operations that modify data should also be retried carefully. Unless the operation is idempotent, retries may result in duplicate processing such as charging a customer twice.

Circuit Breaker Pattern

Repeatedly calling a service that is already failing wastes resources and increases latency.

The Circuit Breaker Pattern detects repeated failures and temporarily stops sending requests to the failing service.
        Application
             |
             v
     Circuit Breaker
         +---+---+
         |       |
     Closed    Open
         |       |
         v       X
      Service  Reject Request
Instead of waiting for repeated failures, the application fails immediately until the downstream service recovers.

Circuit Breaker States

A Circuit Breaker operates in three states.

Closed

Initially, every request is forwarded to the downstream service.
      Application
           |
           v
    Closed Circuit
           |
           v
  Downstream Service
Failures are continuously monitored.

Open

If the failure rate exceeds the configured threshold, the circuit opens.
      Application
           |
           v
     Open Circuit
           |
           X
  Request Rejected
No further requests reach the downstream service until the configured wait period expires.

Half-Open

After the waiting period, a small number of requests are allowed through to test whether the downstream service has recovered.
      Application
           |
           v
      Half Open
           |
   Limited Requests
           |
           v
  Downstream Service
If these requests succeed, the circuit returns to the Closed state. Otherwise, it returns to the Open state.

Circuit Breaker with Resilience4j

Spring Boot integrates easily with Resilience4j.
@CircuitBreaker(name = "paymentService")
public PaymentResponse processPayment() {
    return paymentClient.process();
}
Configuration:
resilience4j:
  circuitbreaker:
    instances:
      paymentService:
        failureRateThreshold: 50
        waitDurationInOpenState: 30s
        slidingWindowSize: 10
If more than 50% of the last ten requests fail, the circuit opens for thirty seconds before allowing test requests.

Using Timeout, Retry, and Circuit Breaker Together

In production systems, Timeout, Retry, and Circuit Breaker are typically applied together to improve resilience.

A request first applies a timeout so that slow downstream services cannot block application threads indefinitely.

If the request fails because of a temporary problem, a limited number of retry attempts are made.

If failures continue and exceed the configured threshold, the Circuit Breaker opens, preventing additional requests from reaching the unhealthy service until it recovers.
          Request
             |
             v
          Timeout
             |
             v
           Retry
             |
             v
     Circuit Breaker
             |
             v
    Downstream Service
This combination prevents unnecessary waiting, avoids overwhelming failing services with repeated requests, and protects application resources.

Spring Boot Example

Suppose an e-commerce application calls a Recommendation Service to retrieve personalized product recommendations.
@Retry(name = "recommendationService")
@CircuitBreaker(
        name = "recommendationService",
        fallbackMethod = "defaultProducts"
)
public List<Product> recommendations() {
    return recommendationClient.getRecommendations();
}

public List<Product> defaultProducts(Exception ex) {
    return productService.getPopularProducts();
}
Configuration:
resilience4j:
  retry:
    instances:
      recommendationService:
        maxAttempts: 3
        waitDuration: 500ms
        enableExponentialBackoff: true
        exponentialBackoffMultiplier: 2

  circuitbreaker:
    instances:
      recommendationService:
        failureRateThreshold: 50
        waitDurationInOpenState: 30s
        slidingWindowSize: 10
The HTTP client should also be configured with a timeout so that slow requests fail quickly instead of blocking application threads.
WebClient.builder()
        .clientConnector(
                new ReactorClientHttpConnector(
                        HttpClient.create()
                                .responseTimeout(Duration.ofSeconds(2))
                )
        )
        .build();
In this example, each request waits for at most 2 seconds before timing out. Temporary failures are retried up to three times using exponential backoff.

If failures continue and the configured threshold is exceeded, the Circuit Breaker opens and immediately invokes the defaultProducts() fallback instead of calling the Recommendation Service.

Fallbacks improve user experience by returning degraded but usable functionality instead of complete failures, allowing the application to continue operating even when downstream services are unavailable.

Advantages

Using these patterns together significantly improves the resilience of distributed systems. Timeouts prevent blocked threads, retries recover from transient failures, and Circuit Breakers stop repeated calls to failing services.

Together they reduce cascading failures, improve application availability, and make better use of system resources during partial outages.

Limitations

Improper configuration can reduce system reliability. Very short timeouts may fail healthy requests, while excessively long timeouts waste resources.

Aggressive retry policies may overload already struggling services, and Circuit Breakers configured with inappropriate thresholds may open too early or remain closed during genuine failures.

Choosing suitable timeout values, retry policies, and failure thresholds requires understanding application traffic patterns and downstream service behavior.

Summary

Timeout, Retry, and Circuit Breaker are complementary resilience patterns that help distributed systems handle failures efficiently.

Timeouts ensure that applications do not wait indefinitely for slow services. Retries recover from temporary failures, while Circuit Breakers prevent repeated requests to services that are already unhealthy.

Spring Boot applications commonly implement these patterns using Resilience4j, often combining them with Bulkhead, Rate Limiting, and Fallback mechanisms to build highly available and fault-tolerant microservices.
Nagesh Chauhan

Nagesh Chauhan

Principal Software Engineer β€’ Java β€’ Python β€’ Distributed Systems β€’ AI/ML

Principal Software Engineer with 14+ years of experience designing and delivering large-scale distributed systems, cloud-native applications, and AI-powered platforms.

Passionate about solving complex engineering problems using strong data structures and algorithms, along with expertise in Java, Spring Boot, Python, System Design, Microservices, Cloud, Kafka, Elasticsearch, and Generative AI.

Share this Article

πŸ’¬ Comments

Join the Discussion