Available Hire Me
← All Writing Spring Boot

Adding Robustness with Spring Retry Annotations

How to use Spring Retry @Retryable and @Backoff in Spring Boot to handle transient failures cleanly — configurable retry logic with exponential backoff, selective exception handling, and real-world examples from Java integration services.

When you’re integrating with external systems — APIs, databases, or third-party services — transient failures are inevitable. A network hiccup, a brief rate-limit window, or a timeout is often enough to cause a failure that would have succeeded a moment later. Rather than littering your code with manual retry loops, Spring Boot provides a clean, declarative solution: the @Retryable annotation.

I’ve used Spring Retry in real-time data pipelines at Mosaic Smart Data, where transient failures from external financial institution APIs were a fact of life. In this post I’ll walk through how I applied it, including exponential backoff and selective exception handling.

Contents


Why Retry?

When a method calls an external API, that call can fail due to reasons beyond our control — temporary outages, throttling, or network latency spikes. In such scenarios, retrying the operation after a delay is often enough to recover gracefully.

Without a retry mechanism, these transient errors can lead to unnecessary failures, noisy logs, and degraded user experience. Implementing retry logic manually can also clutter your code and lead to subtle bugs.

Spring Retry provides a declarative, configurable, and centralized way to handle this.


Enabling Spring Retry

To use Spring Retry, you need to add the dependency and enable it:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Then, enable retry support in your Spring Boot application:


@SpringBootApplication
@EnableRetry
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Using @Retryable on a Service Method

The @Retryable annotation can be placed on any Spring-managed bean method to make it automatically retry on failure.

Here’s the basic structure:


@Retryable(
        maxAttempts = 5,
        backoff = @Backoff(delay = 1000, multiplier = 2.0)
)
public ApiResponse callExternalApi(String param) {
    // potentially flaky operation
}

  • maxAttempts specifies how many times to try before giving up.
  • @Backoff controls the delay between attempts.
    • delay = 1000 means start with a 1-second delay.
    • multiplier = 2.0 means each subsequent retry doubles the delay (exponential backoff).

If the method keeps failing, the last thrown exception is propagated.

Configuring Backoff and Exception Handling

One of the strengths of Spring Retry is that its parameters can be externalized to configuration, allowing different environments (e.g. dev vs prod) to tune retry behavior without code changes.


@Retryable(
        maxAttemptsExpression = "${retry.max.attempts:5}",
        noRetryFor = {ApiException.class, DailyLimitExceededException.class},
        backoff = @Backoff(
                delayExpression = "${retry.backoff.delay:1000}",
                multiplierExpression = "${retry.backoff.multiplier:2.0}"
        )
)
public ApiResponse callExternalApi(String param) {
    ...
}

Key points:

  • maxAttemptsExpression lets you read values from application properties, with 5 as a fallback default.
  • noRetryFor specifies exceptions that should not trigger retries — for example, business logic errors like
  • DailyLimitExceededException should fail immediately.
  • delayExpression and multiplierExpression allow runtime configuration of backoff behavior.

Sample application.yml

retry:
  max:
    attempts: 5
  backoff:
    delay: 1000
    multiplier: 2.0

Practical Example in a Base Provider Service

In my application, I have an abstract base class called ProviderService, which provides common functionality for multiple data provider integrations. One of its key methods, lookup(String lookup), queries an external API.

Here’s how the retry mechanism was applied:


@LogExecutionTime
@Retryable(
        maxAttemptsExpression = "${retry.max.attempts:5}",
        noRetryFor = {ApiException.class, DailyLimitExceededException.class},
        backoff = @Backoff(
                delayExpression = "${retry.backoff.delay:1000}",
                multiplierExpression = "${retry.backoff.multiplier:2.0}"
        )
)
public abstract E lookup(final String lookup)
        throws ApiException, DailyLimitExceededException;

This allows every concrete provider (e.g. CRM, Zone, Prem) to inherit the retry behaviour automatically, without repeating logic in each subclass.

If a lookup fails due to a transient error (e.g. timeout), Spring will automatically retry it up to 5 times with exponential backoff. If the failure is due to a business rule (e.g. daily limit exceeded), the method will fail fast without retries.

Conclusion

Spring Retry is a powerful tool for adding resilience and fault tolerance to your application with minimal code. By using @Retryable:

  • Your service methods stay clean and focused on their core responsibility.
  • Retry behaviour is externalised to configuration — no redeployment needed to tune it.
  • Exceptions that should fail fast (like business rule violations) are excluded cleanly.
  • Exponential backoff is a single annotation attribute.

This small annotation can have a significant impact on the stability and reliability of your integration-heavy services.

Spring Retry Documentation Spring Boot Retry Feature

Samuel Jackson

Samuel Jackson

Senior Java Back End Developer & Contractor

Senior Java Back End Developer — Betfair Exchange API specialist, Spring Boot, AWS, and event-driven architecture. 25+ years delivering high-performance systems across betting, finance, energy, retail, and government. Available for Java contracting.