When a method executes code that may throw an exception, it is usual to hide the exception if is not expected:

public void foo() {
  try {
     // method calls
  } catch(BusinessException /*parent class of business exception*/e) {
    throw e; // message should be shown (or translated and shown) to the user
  } catch(Throwable t /*not a business exception, which means it is not expected */) {
    // log information about t, 
    // home made retry (for tx serialization failure for instance) or:
    throw new RuntimException("an error occured");   // because e may show sensitive details that should not exit the method
  }
}

However if the exception is not a BusinessException its type would not visible to the retry interceptor, which makes fine grained interception configuration impossible. If we want to use @Retryable the best we can do here is excludes = BusinessException.class but then NPE will trigger retry.

How about a rethrow property ?:

@Retryable(includes = CannotAcquireLockException.class, rethrow = RuntimeException.class)
public void foo() {
  try {
     // method calls
  } catch(Throwable t) {
    // apply log policy depending on the exception (business exception thrown by our code vs unexpected)
    // throw t;
  }
}

retry will apply for CannotAcquireLockException, and if the max attemps is reached the exception rethrown by the interceptor to the caller would be an instance of RuntimeException.

Or: @Retryable(includes = CannotAcquireLockException.class, rethrow = @Rethrow(type = RuntimeException.class, message = "..."))

An other option, an ExceptionTranslator (Function<Throwable, Throwable>) could be used:

@Retryable(includes = CannotAcquireLockException.class, translator = MyExceptionTranslator.class)
public void foo() {
  try {
     // method cals
  } catch(Throwable t) {
    // apply log policy depending on the exception (business exception thrown by our code vs unexpected)
    // throw t;
  }
}

The retry interceptor would call the translator and eventually throw what the translation function returns.

Comment From: jhoeller

I'm afraid this is not in the scope of common @Retryable usage. I'd rather recommend a custom exception translation arrangement on top, or programmatic RetryTemplate usage instead where you control what happens in case of a RetryException.