Spring Boot 4 Resilience: A Journey with @Retryable and @ConcurrencyLimit
By Ercan - 12/02/2026
When Spring developers talked about resilience in the past, the answer was usually “add spring-retry as a dependency.” It worked, but it always felt like an add‑on rather than a native part of the framework.
With Spring Framework 7 and Spring Boot 4, that story changes. Retry and concurrency control are now first‑class citizens under the Spring Resilience package. No extra dependencies, no external wiring — just resilience baked right into the core.
I wanted to see how this plays out in practice, so I built a small Proof of Concept project. What follows is the journey of exploring two key annotations: @Retryable and @ConcurrencyLimit.
TL;DR: If you want to skip the explanation and dive straight into the code, check out the implementation on GitHub:
https://github.com/ercansormaz/spring-resilience
Act I: The Concurrency Limit Experiment
Imagine you’re calling an external API that can only handle a couple of requests at a time. What happens if you flood it with parallel calls?
That’s where @ConcurrencyLimit comes in. I created a service with two methods:
- Blocked API Call: annotated with @ConcurrencyLimit(limit=2, policy=BLOCK)
- Rejected API Call: annotated with @ConcurrencyLimit(limit=2, policy=REJECT)
Both simulate a 2‑second API call. The difference is in how they handle overload:
- BLOCK patiently queues extra requests until capacity frees up.
- REJECT simply refuses anything beyond the limit.
Testing endpoints /demo/concurrency-limit/blocked and /demo/concurrency-limit/rejected made the difference crystal clear. BLOCK trickled responses back every two seconds, while REJECT only processed two and discarded the rest.
Act II: Retryable — Declarative Style
Next, I turned to retry logic. The declarative approach with @Retryable feels almost magical: you annotate a method, and resilience is applied automatically.
Here’s what I configured:
- includes: handle
HttpServerErrorExceptionandHttpClientErrorException - maxRetries=5: up to 6 attempts total (initial + 5 retries)
- delayString="100ms": start with a short delay
- multiplier=2: exponential backoff (100ms → 200ms → 400ms …)
- maxDelayString="1000ms": cap the delay at 1 second
- jitterString="100ms": add randomness to avoid synchronized retries
- predicate: custom logic to retry only on specific errors (408 and 503)
Calling /demo/declarative-retryable showed retries in the logs until a successful response was achieved. Declarative resilience, with almost no boilerplate.
Act III: Retryable — Programmatic Style
Sometimes, annotations aren’t enough. You want more control, maybe custom listeners or dynamic policies. That’s where the programmatic approach shines.
Using RetryPolicy.builder(), I mirrored the same configuration as the annotation but with explicit code:
- includes: exceptions to handle
- maxRetries=5: total attempts
- delay: initial delay
- multiplier: exponential backoff
- maxDelay: cap at 1 second
- jitter: random delay variation
- predicate: custom retry decision logic
Then I wrapped it in a RetryTemplate and added a listener to log before each retry. Testing /demo/programmatic-retryable confirmed the flow: retries logged, eventual success returned.
Act IV: The External API Simulation
To make retries meaningful, I built a fake external API at /external/api. It randomly returns 200, 408, or 503.
This setup let me demonstrate how resilience handles both client and server errors. Thanks to the custom predicate, only 408 and 503 triggered retries, while 200 ended the flow immediately.
Conclusion: A New Chapter in Spring
Spring Resilience changes the narrative. What used to be an external dependency is now part of the framework’s DNA.
With @ConcurrencyLimit, you can protect resources from overload. With @Retryable — declarative or programmatic — you can gracefully handle transient failures.
This Proof of Concept is just the beginning. Circuit breakers, bulkheads, and other resilience patterns are waiting to be explored. But even with just retry and concurrency limits, Spring Boot 4 makes building resilient systems simpler, cleaner, and more powerful.
👉 You can explore the source code on GitHub:
https://github.com/ercansormaz/spring-resilience
