Retrying database operations is a need in enterprise applications, especially when dealing with temporary issues such as deadlocks, transient connection failures, race conditions, or brief service outages. Spring provides mechanisms for achieving reliable retries using declarative annotations such as @Retryable together with @Transactional, as well as a fully programmatic approach with RetryTemplate and TransactionTemplate.
Both methods must ensure that each retry is executed within its own transaction, since performing multiple retry attempts inside a single transaction can lead to immediate failure. This happens because an early exception may mark a transaction as rollback-only, causing all remaining attempts to fail even if they would have otherwise succeeded.
This article explains how Spring Retry works with transactional methods and demonstrates both annotation-based and programmatic approaches for achieving reliable retries.
1. Adding Spring Retry to a Project
To use Spring Retry, include the necessary dependency in your 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 in your application
Spring Retry is enabled using the @EnableRetry annotation. The order value determines the precedence of the retry interceptor relative to Springβs transactional interceptor. Setting it to the lowest precedence ensures that retry advice wraps transactional advice, which is essential for isolating each retry into its own new transaction.
@Configuration
@EnableRetry(order = Ordered.LOWEST_PRECEDENCE)
public class RetryConfig {
}
This setup ensures that the retry logic activates first. When a retry occurs, Spring exits the previous transaction, invokes the method again, and starts a fresh transaction for the new attempt.
3. Using @Retryable with @Transactional
A solution for implementing retries is combining Springβs AOP-backed @Retryable annotation with the @Transactional annotation. In this pattern, the retry mechanism wraps the transactional boundary so that every retry attempt executes within a clean, independent transaction.
@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 exception on 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);
}
In this example, the first two attempts intentionally fail by throwing an exception. Because retry advice is applied outside transaction advice, Spring safely rolls back each failed attempt and then triggers a fresh call, each time beginning a new transaction. By the time the method reaches the third attempt, the failure flag from earlier attempts no longer affects it. The third attempt runs inside a clean transaction and proceeds successfully.
4. Programmatic Retry with RetryTemplate and TransactionTemplate
Some use cases require finer control over retry policies, error classification, and transactional boundaries. In such cases, the programmatic approach using RetryTemplate and TransactionTemplate offers more granular control over retry timing, exception handling, and transaction execution.
@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());
// Execute the operation inside a manually controlled transaction
return transactionTemplate.execute(status -> {
Payment payment = new Payment("PENDING", amount);
paymentRepository.save(payment);
// Simulate transient failure for demonstration
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;
});
});
}
}
This example uses RetryTemplate to automatically detect failures, retry up to three times with a 100ms fixed backoff, and provide access to the retry context, including the current attempt count, while TransactionTemplate ensures that each retry executes within a new, isolated transaction, properly rolls back on failure, and prevents any partially committed results.
5. Conclusion
In this article, we explored how to implement reliable transactional retries in Spring using both declarative and programmatic approaches. We demonstrated how @Retryable combined with @Transactional enables clean, annotation-driven retries, and how RetryTemplate with TransactionTemplate provides full control over transaction boundaries, retry policies, and backoff strategies. By ensuring that each retry executes within a new transaction, these strategies prevent rollback side effects from previous failures and guarantee consistent, deterministic outcomes.
6. Download the Source Code
This article explored the use of Spring Retryable with transactional behaviour.
You can download the full source code of this example here: spring retryable with transactional
Thank you!
We will contact you soon.
Omozegie AziegbeDecember 17th, 2025Last Updated: December 17th, 2025

This site uses Akismet to reduce spam. Learn how your comment data is processed.