How to Implement Reliable Transactional Retries in Spring with @Retryable and RetryTemplate

This guide explains how to use Spring Retry's declarative @Retryable annotation together with @Transactional, as well as the programmatic RetryTemplate and TransactionTemplate, to ensure each retry runs in its own transaction and handles transient database failures reliably.

Cognitive Technology Team
Cognitive Technology Team
Cognitive Technology Team
How to Implement Reliable Transactional Retries in Spring with @Retryable and RetryTemplate

In enterprise applications, retrying database operations is essential for handling transient issues such as deadlocks, connection glitches, or brief service interruptions. Spring provides both declarative annotations (@Retryable combined with @Transactional) and a fully programmatic approach using RetryTemplate and TransactionTemplate to achieve reliable retry mechanisms.

1. Add Spring Retry to the project

Include the required dependencies in pom.xml:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>2.0.12</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>

2. Enable Spring Retry

Annotate a configuration class with @EnableRetry. Setting order = Ordered.LOWEST_PRECEDENCE gives the retry interceptor the lowest priority so that it wraps the transaction interceptor, ensuring each retry runs in a new transaction.

@Configuration
@EnableRetry(order = Ordered.LOWEST_PRECEDENCE)
public class RetryConfig {
}

3. Declarative retry with @Retryable

Combine @Retryable and @Transactional on a service method. Each retry is executed in its own transaction, preventing early‑failed attempts from marking the transaction for rollback.

@Retryable(
    maxAttempts = 3,
    backoff = @Backoff(delay = 2000)
)
@Transactional
public void processPayment(double amount) {
    logger.log(Level.INFO, "Attempt #: {0}", attempt);
    Payment payment = new Payment("PENDING", amount);
    paymentRepository.save(payment);
    // Simulate transient failure on the first two attempts
    if (attempt < 3) {
        attempt++;
        throw new RuntimeException("Simulated transient failure");
    }
    payment.setStatus("SUCCESS");
    paymentRepository.save(payment);
    logger.log(Level.INFO, "Payment processed successfully on attempt #: {0}", attempt);
}

4. Programmatic retry with RetryTemplate and TransactionTemplate

For scenarios requiring fine‑grained control over retry policies, exception classification, and transaction boundaries, use the programmatic APIs. The example below creates a RetryTemplate with three attempts and a fixed 100 ms back‑off, then executes the business logic inside a TransactionTemplate.

@Service
public class PaymentServiceProgrammatic {
    private static final Logger logger = Logger.getLogger(PaymentServiceProgrammatic.class.getName());
    private final PaymentRepository paymentRepository;
    private final TransactionTemplate transactionTemplate;

    public PaymentServiceProgrammatic(PaymentRepository paymentRepository,
                                     TransactionTemplate transactionTemplate) {
        this.paymentRepository = paymentRepository;
        this.transactionTemplate = transactionTemplate;
    }

    private final RetryTemplate retryTemplate = new RetryTemplateBuilder()
            .maxAttempts(3)
            .fixedBackoff(Duration.ofMillis(100))
            .build();

    public void processPayment(double amount) {
        retryTemplate.execute(context -> {
            logger.info("Retry attempt: " + context.getRetryCount());
            return transactionTemplate.execute(status -> {
                Payment payment = new Payment("PENDING", amount);
                paymentRepository.save(payment);
                if (context.getRetryCount() < 3) {
                    throw new RuntimeException("Simulated transient failure");
                }
                payment.setStatus("SUCCESS");
                paymentRepository.save(payment);
                logger.info("Payment processed successfully on retry attempt: " + context.getRetryCount());
                return null;
            });
        });
    }
}

5. Conclusion

Both declarative and programmatic approaches allow Spring applications to perform reliable, transactional retries. By ensuring each retry runs in a fresh transaction, the strategies avoid rollback side effects from earlier failures and guarantee consistent outcomes.

Javabackend developmentSpringretryTransactional
Cognitive Technology Team
Written by

Cognitive Technology Team

Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.