In my case, the problem was that a transactional method threw a custom exception that was intended to be caught by another method:
@Transactional
public void foo() throws CustomException {
if (...) throw new CustomException();
}
and in another class:
try {
x.foo()
} catch (CustomException e) {
// do something
}
However, the CustomException cannot be caught because the transaction is aborted as soon as the exception is thrown.
This article was very helpful for debugging.
The solution was to add @Transactional(noRollbackFor = CustomException.class)
to the foo()
method so that the exception is thrown to the caller.