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 by the user 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.