Java — Lets learn few features…Resilience
2 min readJun 20, 2025
While there are many features that we can implement, lets start with few resilience annotations
Retry with
@Retryable(Spring Retry)
- Automatically retries a failed method call.
- Scenarios like retry a flaky payment API up to 3 times before giving up.
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>@SpringBootApplication
@EnableRetry
public class RetryApp {
public static void main(String[] args) {
SpringApplication.run(RetryApp.class, args);
}
}
///////
@Service
public class PaymentService {
@Retryable(
value = { IOException.class },
maxAttempts = 3,
backoff = @Backoff(delay = 2000))
public void callPaymentGateway() throws IOException {
System.out.println("Calling payment gateway...");
if (Math.random() < 0.7) throw new IOException("Temporary network issue");
System.out.println("Payment processed successfully");
}
@Recover
public void recover(IOException e) {
System.out.println("Recovering after retries failed: " + e.getMessage());
}
}
Circuit Breaker with Resilience4j
- Prevents repeated calls to a failing service. Automatically closes after some time.
- Scenarios like stop calling a product service if it fails 3 times in a row.
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>@Service
public class ProductService {
@CircuitBreaker(name = "productService", fallbackMethod = "fallback")
public String getProduct() {
System.out.println("Calling external product API...");
if (Math.random() < 0.7) throw new RuntimeException("Service unavailable");
return "Product info";
}
public String fallback(Throwable t) {
return "Fallback product info (cached)";
}
}resilience4j.circuitbreaker:
instances:
productService:
registerHealthIndicator: true
slidingWindowSize: 5
failureRateThreshold: 50
waitDurationInOpenState: 5sRetry vs Circuit Breaker
Retry — optimistic — “Let’s keep trying!”
- You expect the failure to be transient (e.g., network timeout, temporary rate-limit).
- Retrying increases the chance of success.
Circuit Breaker — protective — “Let’s stop before we crash!”
- You want to stop calling a service that is consistently failing.
- You’re protecting your system from further harm or overload.
Can You Use Both Together?
Get Naveen’s stories in your inbox
Join Medium for free to get updates from this writer.
Yes!
- Retry for quick recovery from transient errors.
- Circuit Breaker to prevent retrying indefinitely and causing more damage.
@Retry(name = "inventoryRetry")
@CircuitBreaker(name = "inventoryCB", fallbackMethod = "fallback")
public String callInventoryService() {
// External API call
}
@RateLimiter(Resilience4j)
- Limit how many requests can be served over time (per user, per API, etc.)
- Scenarios like allow max 5 requests per user per minute.
- We can combine redis as well to implement
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
</dependency>@RestController
public class RateLimitedController {
private final Bucket bucket;
public RateLimitedController() {
Bandwidth limit = Bandwidth.simple(5, Duration.ofMinutes(1));
this.bucket = Bucket4j.builder().addLimit(limit).build();
}
@GetMapping("/api/limited")
public ResponseEntity<String> call() {
if (bucket.tryConsume(1)) {
return ResponseEntity.ok("Request processed");
} else {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("Too many requests");
}
}
}
@Bulkhead(Resilience4j)
- Limits concurrent calls to a method (thread isolation).
- Prevents thread exhaustion.
- limit the number of concurrent requests to a specific part of your system, isolating failures and preventing cascading failures
@TimeLimiter(Resilience4j)
- Limits how long a method can take.
- Forces timeout for long-running calls.










