Backend Development 6 min read

Custom @FeignRetry Annotation for Per‑Method Retry in Feign Clients Using Spring Retry

This article explains how to create a custom @FeignRetry annotation that leverages Spring Retry to provide configurable, per‑method retry policies for Feign client calls, including code examples of the annotation, aspect implementation, and usage patterns.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Custom @FeignRetry Annotation for Per‑Method Retry in Feign Clients Using Spring Retry

Custom @FeignRetry Annotation

In our company, services communicate via Feign, but the built‑in Feign retry mechanism applies globally and cannot be configured per method. By combining Spring‑Retry with Feign, we can define a custom @FeignRetry annotation that allows different retry policies for each Feign client method.

Definition of @FeignRetry

The annotation mirrors @Retryable and includes attributes such as backoff, maxAttempt, and include. Its source code is shown below.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignRetry {

    Backoff backoff() default @Backoff();
    int maxAttempt() default 3;
    Class
[] include() default {};
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Backoff {
    long delay() default 1000L;;
    long maxDelay() default 0L;;
    double multiplier() default 0.0D;;
}

The corresponding aspect FeignRetryAspect intercepts methods annotated with @FeignRetry , builds a RetryTemplate with the specified back‑off and retry policies, and executes the original call.

Slf4j
@Aspect
@Component
public class FeignRetryAspect {

    @Around("@annotation(FeignRetry)")
    public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getCurrentMethod(joinPoint);
        FeignRetry feignRetry = method.getAnnotation(FeignRetry.class);

        RetryTemplate retryTemplate = new RetryTemplate();
        retryTemplate.setBackOffPolicy(prepareBackOffPolicy(feignRetry));
        retryTemplate.setRetryPolicy(prepareSimpleRetryPolicy(feignRetry));

        // retry
        return retryTemplate.execute(arg0 -> {
            int retryCount = arg0.getRetryCount();
            log.info("Sending request method: {}, max attempt: {}, delay: {}, retryCount: {}",
                    method.getName(),
                    feignRetry.maxAttempt(),
                    feignRetry.backoff().delay(),
                    retryCount
            );
            return joinPoint.proceed(joinPoint.getArgs());
        });
    }

    private BackOffPolicy prepareBackOffPolicy(FeignRetry feignRetry) {
        if (feignRetry.backoff().multiplier() != 0) {
            ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
            backOffPolicy.setInitialInterval(feignRetry.backoff().delay());
            backOffPolicy.setMaxInterval(feignRetry.backoff().maxDelay());
            backOffPolicy.setMultiplier(feignRetry.backoff().multiplier());
            return backOffPolicy;
        } else {
            FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
            fixedBackOffPolicy.setBackOffPeriod(feignRetry.backoff().delay());
            return fixedBackOffPolicy;
        }
    }

    private SimpleRetryPolicy prepareSimpleRetryPolicy(FeignRetry feignRetry) {
        Map
, Boolean> policyMap = new HashMap<>();
        policyMap.put(RetryableException.class, true); // Connection refused or time out
        policyMap.put(ClientException.class, true); // Load balance does not available (cause of RunTimeException)
        if (feignRetry.include().length != 0) {
            for (Class
t : feignRetry.include()) {
                policyMap.put(t, true);
            }
        }
        return new SimpleRetryPolicy(feignRetry.maxAttempt(), policyMap, true);
    }

    private Method getCurrentMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod();
    }
}

The aspect captures methods annotated with @FeignRetry , passes the configuration to Spring's RetryTemplate , and invokes the target service according to the defined retry strategy.

Usage of @FeignRetry

To enable retry for a specific Feign client method, simply place @FeignRetry on the method and specify the desired attributes. The example below demonstrates two methods with different retry configurations.

@GetMapping
@FeignRetry(maxAttempt = 3, backoff = @Backoff(delay = 500L))
ResponseEntity
retrieve1();

@GetMapping
@FeignRetry(maxAttempt = 6, backoff = @Backoff(delay = 500L, maxDelay = 20000L, multiplier = 4))
ResponseEntity
retrieve2();

Additionally, add @EnableRetry to the Spring Boot application class to activate Spring Retry support.

Conclusion

Feign retry is a common scenario; by defining a custom @FeignRetry annotation, developers can apply fine‑grained, per‑method retry strategies to Feign interfaces, making the solution flexible and easy to integrate into existing projects.

Javaaopfeignannotationsretry mechanismSpring Retry
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

login 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.