Spring Retry does not work on 2nd level of methods

So this is a super late answer, but since I've just come here and confronted the same problem (again, after years ago wrestling with transactions) I'll furnish a little more fleshed out solution and hopefully someone will find it useful. Suffice to say that @M. Deinum's diagnosis is correct.

enter image description here

enter image description here

In the above case, and to paraphrase Understanding AOP proxies, any place where SphIptvClient gets autowired will be given a reference to a proxy which Spring Retry will create when @EnableRetry is handled:

"The @EnableRetry annotation creates proxies for @Retryable beans" - Declarative Retry - Spring Retry

Once getSubscriberAccount has been invoked and execution has passed through the proxy and into the @Service instance of the object, no reference to the proxy is known. As a result sphRemoteCall is called as if there were no @Retryable at all.

You could work with the framework by shuffling code around in such a way as to allow getSubscriberAccount to call a proxy-ed sphRemoteCall, which requires a new interface and class implementation.

For example:

public interface SphWebService {
   Object sphRemoteCall(String uri, Object requestPayload, String soapAction);
}

@Component
public class SphWebServiceImpl implements SphWebService {
   @Retryable
   public Object sphRemoteCall(String uri, Object requestPayload, String soapAction) {
       log.debug("Calling the sph for uri:{} and soapAction:{}", uri, soapAction);
       return getWebServiceTemplate().marshalSendAndReceive(uri, requestPayload, new SoapActionCallback(soapAction));
   }
}

@Service
public class SphIptvClient extends WebServiceGatewaySupport {

   @Autowired
   SphWebService sphWebService;

   @Retryable(maxAttempts=3, backoff=@Backoff(delay=100))
   public GetSubscriberAccountResponse getSubscriberAccount(String loginTocken, String billingServId) {
       GetSubscriberAccountResponse response = (GetSubscriberAccountResponse) this.sphWebService.sphRemoteCall(sphIptvEndPoint, getSubAcc, "xxxxx");
       return response;
   }
}

@Configuration
@EnableRetry
public class SphClientConfig {
   // the @Bean method was unnecessary and may cause confusion. 
   // @Service was already instantiating SphIptvClient behind the scenes.
}

@Retryable only works on the methods when called directly from other classes. If you will try to invoke one method with @Retryable annotation from some other method of the same class, it will eventually not work.

    // any call from this method to test method will not invoke the retry logic.
    public void yetAnotherMethod() {
        this.test();
    }

    // it will work
    @Retryable(value = {RuntimeException.class}, backoff = @Backoff(delay = 1500))
    public void test() {
        System.out.println("Count: " + count++);
        throw new RuntimeException("testing");
    }

    @Recover
    public void recover() {
        System.out.println("Exception occured.");
    }

So, the output if test method is called, will be:

Count: 0
Count: 1
Count: 2
Exception occured.

But, if the yetAnotherMethod is called, output will be:
Count: 0
And a Runtime exception will be thrown.

Tags:

Java

Spring