Mastering Retry Strategies in Spring Boot 3: 5 Practical Components
This article explores the growing importance of retry mechanisms in distributed Spring Boot 3 applications, presenting five practical retry components—Spring‑Retry, Resilience4j‑Retry, Fast‑Retry, Easy‑Retry, and Guava‑Retrying—along with custom annotation examples, configuration details, code snippets, and execution results.
1. Introduction
In modern distributed systems and micro‑service architectures, inter‑service communication complexity leads to frequent network latency, timeouts, and transient failures such as database disconnections or third‑party API errors. To improve resilience, retry logic is often extracted from business code and implemented as a cross‑cutting concern using tools like Spring Retry or Resilience4j, which support declarative configuration, exponential back‑off, jitter, and integration with circuit breakers and rate limiters.
2. Practical Cases
2.1 Spring‑Retry
Spring‑Retry is an official Spring project that provides declarative retry support for Spring Batch, Spring Integration, and other modules.
Dependency
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>Enable Retry
@EnableRetry
public class AppConfig {}Usage Example
@Retryable(recover = "deductStockRecover", retryFor = StockDeductException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public void deductStock(Stock stock, Integer count) {
System.out.printf("准备扣减【%s】商品, 总数: %d%n", stock.name(), count);
if (stock.quantity() < count) {
throw new StockDeductException("库存不足");
}
// ...
}
@Recover
public void deductStockRecover(StockDeductException e, Stock stock, Integer count) {
System.err.printf("【%s】库存扣减失败, 库存不足: %d个%n", stock.name(), count);
}Run result:
2.2 Resilience4j‑Retry
Resilience4j is a lightweight fault‑tolerance library designed for functional programming, offering retry, circuit‑breaker, rate‑limiter, bulkhead, and more.
Dependency
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.2.0</version>
</dependency>Usage Example
@Retry(name = "stock-deduct", fallbackMethod = "stockDeductFallback")
public void deductStock(Stock stock, Integer count) {
System.out.printf("准备扣减【%s】商品, 总数: %d%n", stock.name(), count);
if (stock.quantity() < count) {
throw new StockDeductException("库存不足");
}
// ...
}
public void stockDeductFallback(Stock stock, Integer count, Throwable e) {
System.err.printf("resilience4j-retry, 【%s】库存扣减失败, 库存不足: %d个%n", stock.name(), count);
}Configuration (application.yml):
resilience4j.retry:
instances:
stock-deduct:
max-attempts: 3
wait-duration: 1000
exponential-backoff-multiplier: 1Run result:
2.3 Fast‑Retry
Fast‑Retry is a high‑performance asynchronous retry framework capable of handling millions of concurrent tasks, supporting timeout, callbacks, and back‑off strategies.
Dependency
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>fast-retry-spring</artifactId>
<version>0.3.2</version>
</dependency>Enable Fast‑Retry
@EnableFastRetry
public class AppConfig {}Usage Example
@FastRetry(
retryWait = @RetryWait(delay = 1, timeUnit = TimeUnit.SECONDS),
exceptionRecover = true,
maxAttempts = 2,
briefErrorLog = true)
public void deductStock(Stock stock, Integer count) {
System.out.printf("准备扣减【%s】商品, 总数: %d%n", stock.name(), count);
if (stock.quantity() < count) {
throw new StockDeductException("库存不足");
}
// ...
}Run result:
2.4 Easy‑Retry
Easy‑Retry, originally from Alibaba, provides a memory‑based persistent retry solution (now deprecated for Spring Boot 3, tested on Spring Boot 2.7).
Dependencies
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easy-retry-memory-starter</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>Enable Memory‑Based Configuration
spring:
easyretry:
memory:
enabled: true
maxRetryTimes: 2Usage Example
@EasyRetryable
public void deductStock(Stock stock, Integer count) {
System.out.printf("准备扣减【%s】商品, 总数: %d%n", stock.getName(), count);
if (stock.getQuantity() < count) {
throw new StockDeductException("库存不足");
}
// ...
}Run result:
2.5 Guava‑Retrying
Guava‑Retrying offers a generic retry mechanism for any Java code, enhanced with Guava predicates for stop conditions and retry strategies.
Dependency
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>Usage Example
private static final Retryer<Object> retryer = RetryerBuilder.<Object>newBuilder()
.retryIfException()
.withWaitStrategy(WaitStrategies.exponentialWait(100, 200, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build();
public void deductStock(Stock stock, Integer count) {
try {
retryer.call(() -> {
System.out.printf("准备扣减【%s】商品, 总数: %d%n", stock.name(), count);
if (stock.quantity() < count) {
throw new StockDeductException("库存不足");
}
return null;
});
} catch (RetryException | ExecutionException e) {
System.err.println(e.getMessage());
}
}Run result:
2.6 Custom Retry Component
Define a custom annotation and AOP aspect to control retry behavior.
Custom Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PackRetry {
int maxAttempts() default 3;
long retryInterval() default 1000;
Class<? extends Throwable>[] retryFor() default {Exception.class};
}Retry Aspect
@Aspect
@Component
public class PackRetryAspect {
private static final int DEFAULT_MAX_RETRIES = 3;
@Around("@annotation(packRetry)")
public Object retryOperator(ProceedingJoinPoint pjp, PackRetry packRetry) throws Throwable {
long interval = packRetry.retryInterval();
Class<? extends Throwable>[] retryExceptions = packRetry.retryFor();
int maxAttempts = packRetry.maxAttempts() + 1;
int maxRetries = maxAttempts < 0 ? DEFAULT_MAX_RETRIES : maxAttempts;
int numAttempts = 0;
Throwable exception;
do {
numAttempts++;
try {
return pjp.proceed();
} catch (Throwable ex) {
exception = ex;
if (!shouldRetry(ex, retryExceptions) || numAttempts >= maxAttempts) {
break;
}
retryWait(interval, numAttempts, exception);
}
} while (numAttempts <= maxRetries);
throw exception;
}
private boolean shouldRetry(Throwable ex, Class<? extends Throwable>[] retryExceptions) {
return Arrays.stream(retryExceptions).anyMatch(cls -> cls.isInstance(ex));
}
private void retryWait(long interval, int attempt, Throwable ex) {
try {
synchronized (this) { wait(interval); }
System.out.printf("→ Retry #%d after %dms (cause: %s)%n", attempt, interval, ex.getClass().getSimpleName());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", e);
}
}
}Usage Example
@PackRetry
public void deductStock(Stock stock, Integer count) {
System.out.printf("准备扣减【%s】商品, 总数: %d%n", stock.name(), count);
if (stock.quantity() < count) {
throw new StockDeductException("库存不足");
}
}Run result:
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
