Mastering Retry Strategies in Java: From Manual Loops to Spring Retry and Guava

This article walks through various Java retry mechanisms—including manual loops, static and dynamic proxies, AOP, Spring Retry annotations, and Guava‑retry—explaining their implementations, advantages, drawbacks, and when to choose each approach for robust remote service calls.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Mastering Retry Strategies in Java: From Manual Loops to Spring Retry and Guava

1. Manual Retry

Manual retry uses a simple while loop to repeat a remote call, but it lacks interval control, introduces invasive code, and leads to duplicated logic across services.

@Service
public class OrderServiceImpl implements OrderService {
    public void addOrder() {
        int times = 1;
        while (times <= 5) {
            try {
                // intentionally cause exception
                int i = 3 / 0;
                System.out.println("重试" + times + "次");
                Thread.sleep(2000);
                times++;
                if (times > 5) {
                    throw new RuntimeException("不再重试!");
                }
            } catch (Exception e) {
                // handle exception (already logged)
            }
        }
    }
}

Running the code demonstrates the retry attempts but also shows the shortcomings mentioned above.

Manual retry illustration
Manual retry illustration

2. Static Proxy

A static proxy wraps the business class with a proxy that contains the retry logic, keeping the original business code untouched while centralising retry handling. However, a separate proxy class is required for each service.

@Service
public class OrderServiceProxyImpl implements OrderService {
    @Autowired
    private OrderServiceImpl orderService;

    @Override
    public void addOrder() {
        int times = 1;
        while (times <= 5) {
            try {
                // intentionally cause exception
                int i = 3 / 0;
                orderService.addOrder();
            } catch (Exception e) {
                System.out.println("重试" + times + "次");
                Thread.sleep(2000);
                times++;
                if (times > 5) {
                    throw new RuntimeException("不再重试!");
                }
            }
        }
    }
}

3. JDK Dynamic Proxy

JDK dynamic proxy allows a single InvocationHandler to add retry logic for any interface, reducing boilerplate code.

public class RetryInvocationHandler implements InvocationHandler {
    private final Object subject;
    public RetryInvocationHandler(Object subject) {
        this.subject = subject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        int times = 1;
        while (times <= 5) {
            try {
                // intentionally cause exception
                int i = 3 / 0;
                return method.invoke(subject, args);
            } catch (Exception e) {
                System.out.println("重试【" + times + "】次");
                Thread.sleep(2000);
                times++;
                if (times > 5) {
                    throw new RuntimeException("不再重试!");
                }
            }
        }
        return null;
    }
    public static Object getProxy(Object realSubject) {
        InvocationHandler handler = new RetryInvocationHandler(realSubject);
        return Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
    }
}

4. CGLib Dynamic Proxy

CGLib proxy works for classes that do not implement an interface, overcoming the limitation of JDK proxies.

@Component
public class CGLibRetryProxyHandler implements MethodInterceptor {
    private Object target;
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        int times = 1;
        while (times <= 5) {
            try {
                // intentionally cause exception
                int i = 3 / 0;
                return method.invoke(target, args);
            } catch (Exception e) {
                System.out.println("重试【" + times + "】次");
                Thread.sleep(2000);
                times++;
                if (times > 5) {
                    throw new RuntimeException("不再重试!");
                }
            }
        }
        return null;
    }
    public Object getCglibProxy(Object objectTarget) {
        this.target = objectTarget;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(objectTarget.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
}

5. Manual AOP

Define a custom annotation and an Aspect to weave retry logic around annotated methods, eliminating repetitive code.

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRetryable {
    int retryTimes() default 3; // maximum attempts
    int retryInterval() default 1; // seconds between attempts
}

@Aspect
@Component
public class RetryAspect {
    @Pointcut("@annotation(com.hcr.sbes.retry.annotation.MyRetryable)")
    private void retryMethodCall() {}

    @Around("retryMethodCall()")
    public Object retry(ProceedingJoinPoint joinPoint) throws InterruptedException {
        MyRetryable retry = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(MyRetryable.class);
        int maxRetryTimes = retry.retryTimes();
        int retryInterval = retry.retryInterval();
        Throwable error = new RuntimeException();
        for (int i = 1; i <= maxRetryTimes; i++) {
            try {
                return joinPoint.proceed();
            } catch (Throwable t) {
                error = t;
                log.warn("调用发生异常,开始重试,retryTimes:{}", i);
                Thread.sleep(retryInterval * 1000L);
            }
        }
        throw new RuntimeException("重试次数耗尽", error);
    }
}

6. Spring Retry

Spring Retry provides three annotations— @Retryable, @Backoff, and @Recover —to configure retry attempts, back‑off strategies, and recovery handling.

@Service
public class OrderServiceImpl implements OrderService {
    @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier = 2))
    public void addOrder() {
        System.out.println("重试...");
        int i = 3 / 0;
    }

    @Recover
    public void recover(RuntimeException e) {
        log.error("达到最大重试次数", e);
    }
}

7. Guava Retry

Guava‑retry offers flexible policies that can react to exceptions or specific return values, with customizable wait and stop strategies.

public String guavaRetry(Integer num) {
    Retryer<String> retryer = RetryerBuilder.<String>newBuilder()
        .retryIfException()
        .retryIfResult(result -> Objects.equals(result, "error"))
        .withWaitStrategy(WaitStrategies.fixedWait(2, TimeUnit.SECONDS))
        .withStopStrategy(StopStrategies.stopAfterAttempt(3))
        .withRetryListener(new RetryListener() {
            @Override
            public <V> void onRetry(Attempt<V> attempt) {
                System.out.println("RetryListener: 第" + attempt.getAttemptNumber() + "次调用");
            }
        })
        .build();
    try {
        retryer.call(() -> testGuavaRetry(num));
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "test";
}

Summary

For Spring‑based projects, using Spring Retry annotations solves most retry requirements. For non‑Spring environments, Guava‑retry provides a powerful, framework‑agnostic alternative, while dynamic proxies and manual AOP give fine‑grained control when needed.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaAOPspringretryguavaDynamic Proxycglib
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.