VOOZH about

URL: https://www.javacodegeeks.com/spring-retryable-with-transactional.html

⇱ Spring @Retryable With @Transactional - Java Code Geeks


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.

Download
You can download the full source code of this example here: spring retryable with transactional
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Thank you!

We will contact you soon.

πŸ‘ Photo of Omozegie Aziegbe
Omozegie Aziegbe
December 17th, 2025Last Updated: December 17th, 2025
0 573 3 minutes read

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe

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

0 Comments
Oldest
Newest Most Voted
Back to top button
Close
wpDiscuz