Master Spring Retry: Annotation & Programmatic Retries in SpringBoot 2.7
This tutorial explains how to use Spring Retry in a SpringBoot 2.7 application, covering both annotation‑based @Retryable usage and programmatic RetryTemplate configuration, with complete code examples, listener implementation, and execution results.
1. Introduction
Spring Retry is a component provided by the Spring framework for method retrying. It is built on Spring AOP and can handle method call failures caused by network issues or other transient problems. The core idea is to retry when a method fails according to a configured RetryPolicy , which defines whether a retry can be triggered, which exceptions are retryable, and how long to wait before retrying. Spring Retry offers various strategies such as BackOffPolicy . The @Retryable annotation can be placed directly on methods, and RetryTemplate provides more flexible control, while callbacks can be implemented via the RetryCallback interface.
2. Practical Example
2.1 Annotation‑based
Dependency
<code><dependencies>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies></code>Entity and DAO
<code>@Entity
@Table(name = "t_account")
public class Account {
@Id
private Long id;
private String userId;
private BigDecimal money;
}</code>Repository interface
<code>public interface AccountRepository extends JpaRepository<Account, Long> {
Account findByUserId(String userId);
}</code>Service class
<code>@Service
public class AccountService {
@Resource
private AccountRepository ar;
@Transactional
@Retryable(value = {InvalidParameterException.class})
public Account save(Account account) {
System.out.println(Thread.currentThread().getName());
if (account.getId() == 0) {
System.out.println("save retry...");
throw new InvalidParameterException("Invalid parameter");
}
return ar.saveAndFlush(account);
}
// Callback after retries are exhausted
@Recover
public Account recoverSave(Exception e) {
System.out.println("............." + e.getMessage());
return null;
}
}</code>Annotation attributes description
@Retryable attributes include:
recover : name of the fallback method; if omitted, Spring matches a method annotated with @Recover whose first parameter is the retry exception.
interceptor : specify a method‑level bean implementing MethodInterceptor .
value / include : exception types to retry; if both are empty, all exceptions are retried.
exclude : exception types that should not be retried.
label : a unique label for statistics.
stateful : default false ; indicates whether retries are stateful.
maxAttempts : maximum retry attempts, default is 3.
backoff : specify @Backoff for delay strategy.
Backoff configuration illustration
listeners : specify beans implementing org.springframework.retry.RetryListener . Example listener implementation:
<code>public class AccountRetryListener implements RetryListener {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println("open invoke...");
return false; // returning false prevents retry
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
System.out.println("close invoke...");
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
System.out.println("onError invoke...");
throwable.getStackTrace();
}
}</code>Enabling retry functionality
<code>@Configuration
@EnableRetry
public class RetryConfig {
}</code>Test case for annotation‑based retry
<code>@Resource
private AccountService accountService;
@Test
public void testSave() {
Account account = new Account();
account.setId(0L);
account.setMoney(BigDecimal.valueOf(1000));
account.setUserId("1");
accountService.save(account);
}</code>Console output shows three retry attempts (default maxAttempts = 3).
2.2 Programmatic (imperative) retry
Custom RetryTemplate configuration
<code>@Configuration
@EnableRetry
public class RetryConfig {
@Bean
public RetryTemplate retryTemplate() {
return RetryTemplate.builder()
.maxAttempts(3) // retry count
.fixedBackoff(1000) // fixed interval 1000ms
.retryOn(InvalidParameterException.class) // retry on specific exception
.build();
}
}</code>Modified service using RetryTemplate
<code>public class AccountService {
@Resource
private AccountDAO accountDAO;
@Resource
private RetryTemplate retryTemplate;
@Transactional
public Account update(Account account) {
return retryTemplate.execute(context -> {
if (account.getId() == 0) {
System.out.println("update retry...");
throw new InvalidParameterException("Invalid parameter");
}
return accountDAO.saveAndFlush(account);
}, context -> {
System.out.println("retry finished...");
return null;
});
}
}</code>Test case for programmatic retry
<code>@Test
public void testUpdate() {
Account account = new Account();
account.setId(0L);
account.setMoney(BigDecimal.valueOf(1000));
account.setUserId("1");
accountService.update(account);
}</code>Execution result shows three retry attempts followed by the fallback message.
<code>update retry...
update retry...
update retry...
retry finished...</code>RetryContext and Recovery Callback
<code>private static int index = 0;
public static String save() {
logger.info("Executing save task...");
System.out.println(1 / 0); // will throw
return "success";
}
RetryTemplate template = RetryTemplate.builder()
.maxAttempts(3)
.retryOn(Exception.class)
.customBackoff(BackOffPolicyBuilder.newBuilder().delay(100).multiplier(3).build())
.build();
String result = template.execute(context -> {
context.setAttribute("c", index++);
System.out.println(context.getAttribute("c"));
return save();
}, new RecoveryCallback<String>() {
@Override
public String recover(RetryContext context) throws Exception {
return "Default system value";
}
});
System.out.println("Result: " + result);
</code>Execution logs show three attempts and the final recovered result.
<code>0
... INFO - Executing save task...
1
... INFO - Executing save task...
2
... INFO - Executing save task...
Result: Default system value</code>The article concludes with a hope that the content is helpful.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.