Sitemap

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: 5s

Retry 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.

--

--