How do you make a Spring transaction roll back when a checked exception is thrown?
answer
- rollbackFor = Exception.class
- noRollbackFor to exclude
- setRollbackOnly() programmatic
- or rethrow as RuntimeException
- closest-match depth wins
basics
~10 sSet rollbackFor on the annotation: @Transactional(rollbackFor = Exception.class) rolls back on any exception, or name a specific type like rollbackFor = MyCheckedException.class. Alternatively, throw an unchecked exception instead.
solid answer
~40 sThe declarative fix is @Transactional(rollbackFor = Exception.class), which extends the rollback rule to all exceptions including checked ones. You can narrow it to specific types, e.g. rollbackFor = { PaymentException.class, IOException.class }, and conversely use noRollbackFor to exclude types even if they'd otherwise roll back. A second approach is to not use checked exceptions to signal transactional failure at all — wrap or rethrow as an unchecked exception (e.g. a custom RuntimeException), which rolls back under the default rule. A third, programmatic option is TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() inside a catch block, which flags the current transaction for rollback without throwing. Teams usually standardise on one policy — many set rollbackFor = Exception.class globally to make behaviour predictable.
code
java · 27 lines@Service
public class OrderService {
// FIX 1: declarative opt-in
@Transactional(rollbackFor = OrderException.class)
public void placeOrder(Order o) throws OrderException {
repository.save(o);
if (!inventory.reserve(o)) throw new OrderException("out of stock");
// now the save IS rolled back
}
// FIX 2: programmatic
@Transactional
public void placeOrder2(Order o) {
repository.save(o);
try {
legacyReserve(o); // throws checked IOException
} catch (java.io.IOException e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
static class OrderException extends Exception {
OrderException(String m) { super(m); }
}
}go deeper
Knows rollbackFor = Exception.class as the fix.
Can name all three approaches and when each applies.
Explains rule-matching depth and the swallow-the-exception caveat.
Discusses standardising rollback policy via meta-annotations and the trade-off of a blanket rollbackFor = Exception.class.
## Three ways to force rollback on checked exceptions ### 1. Declarative: `rollbackFor` The `@Transactional` annotation accepts `rollbackFor` (and `rollbackForClassName` for string matching). This adds a **rollback rule** for the named types on top of the default: ```java @Transactional(rollbackFor = Exception.class) public void doWork() throws MyCheckedException { ... } ``` `Exception.class` is the broadest useful value — it matches every checked and unchecked exception (but not `Error`, which is already covered by the default and by `Throwable.class` if you want it). You can also list specific types: ```java @Transactional(rollbackFor = { PaymentFailedException.class, IOException.class }) ``` ### 2. Declarative exclusion: `noRollbackFor` The inverse — commit even for an exception that would otherwise roll back: ```java @Transactional(noRollbackFor = ValidationWarning.class) ``` Useful when a particular `RuntimeException` subtype should be treated as a non-fatal business outcome. ### 3. Programmatic: `setRollbackOnly()` Inside a `catch` block you can mark the current transaction for rollback without rethrowing: ```java import org.springframework.transaction.interceptor.TransactionAspectSupport; try { risky(); } catch (MyCheckedException e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // handle / log; method returns normally but tx will roll back } ``` The transaction is then guaranteed to roll back at commit time; if the caller also participates it typically gets an `UnexpectedRollbackException`. ### 4. Design alternative: don't use checked exceptions for tx failures Because the default already rolls back unchecked exceptions, many teams model failures as `RuntimeException` subclasses. Catching a low-level checked exception and rethrowing as an unchecked one (Spring itself does this — `DataAccessException` is an unchecked wrapper over `SQLException`) yields rollback for free. ## Rule-matching semantics When you list several `rollbackFor`/`noRollbackFor` types, Spring picks the rule whose target type is the **closest (shallowest) match** in the thrown exception's superclass hierarchy — implemented via `RollbackRuleAttribute.getDepth(...)`. The winning rule (rollback vs. no-rollback) is the one with the smallest depth. This lets a broad `rollbackFor = Exception.class` coexist with a specific `noRollbackFor = SomeSubException.class`. ## Where to configure it - Method or class level on `@Transactional`. - Globally, some teams define a shared transaction advice / customise the `TransactionAttributeSource`, or simply put `@Transactional(rollbackFor = Exception.class)` on a base annotation (a meta-annotation) so every service uses the same policy. ## Gotcha `rollbackFor` only matters if the exception actually **propagates out of the proxied method**. If you catch it inside and swallow it (no rethrow, no `setRollbackOnly()`), no rollback happens regardless of `rollbackFor`.
- What's the difference between rollbackFor = Exception.class and rollbackFor = Throwable.class?Exception.class covers all checked and unchecked exceptions but not Error. Throwable.class additionally covers Error (e.g. OutOfMemoryError) — but Error already rolls back under the default rule, so the practical difference is negligible; Exception.class is the common choice.
- If you catch the checked exception inside the method and don't rethrow, does rollbackFor help?No. The rollback rule is only evaluated for exceptions that propagate out of the proxied method. A swallowed exception leaves the tx to commit unless you call setRollbackOnly().
saying these in an interview costs you the question
- Saying rollbackFor changes behaviour even when the exception is caught and swallowed inside the method
- Thinking noRollbackFor is required to commit on checked exceptions (that's already the default)
- Confusing rollbackFor (add rollback) with noRollbackFor (suppress rollback)