skip to content

rollbackFor / noRollbackFor Rules

By default Spring rolls back on unchecked exceptions and commits on checked ones, with rollbackFor and noRollbackFor to override. That default surprises people, which is exactly why it is such a common interview question.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

By default, which exceptions cause a Spring @Transactional method to roll back, and which let it commit?

level: juniorimportance: must knowfreq 80%

answer

  1. Unchecked + Error -> rollback
  2. Checked -> commit (EJB legacy)
  3. DefaultTransactionAttribute.rollbackOn
  4. Must propagate out of the method
  5. DataAccessException is unchecked

basics

~10 s

By default Spring rolls back on unchecked exceptions (RuntimeException) and Error. It commits on checked exceptions (any Exception that isn't a RuntimeException). You must opt in to roll back on checked exceptions.

solid answer

~40 s

Spring's declarative transactions use a rule: roll back automatically only when a RuntimeException (unchecked) or an Error propagates out of the @Transactional method. If a checked exception (a subclass of Exception that is not a RuntimeException) escapes, Spring commits the transaction. This mirrors EJB's historical convention. It surprises people: throwing a checked exception like IOException or a custom checked business exception commits your work unless you override the rule with rollbackFor. The exception must actually propagate out of the method boundary — if you catch it inside, there's no rollback (and no commit-vs-rollback decision on it at all). To roll back on checked exceptions, use @Transactional(rollbackFor = ...).

code

java · 18 lines
java
@Service
public class TransferService {

    // Custom CHECKED exception
    public static class BusinessException extends Exception {}

    @Transactional
    public void transfer() throws BusinessException {
        debitAccount();          // partial write
        throw new BusinessException(); // checked -> Spring COMMITS the debit!
    }

    @Transactional
    public void charge() {
        debitAccount();
        throw new IllegalStateException(); // unchecked -> Spring ROLLS BACK
    }
}

go deeper

for a junior

Must be able to state the rule crisply: unchecked + Error roll back, checked commit.

for a middle

Should connect it to the EJB origin and know DataAccessException is unchecked.

for a senior

Should stress the 'must propagate out' subtlety and the swallowed-exception trap.

for a principal

Frames it as a design default worth overriding project-wide; may standardize on rollbackFor=Exception.class or RuntimeException-based domain exceptions.

## The default rollback rule When you annotate a method with `@Transactional`, Spring wraps it in a proxy that starts a transaction, runs the method, and then decides to **commit** or **roll back** based on how the method returns. The default decision is: - **Roll back** if the method throws a **`RuntimeException`** (unchecked) or an **`Error`**. - **Commit** if the method returns normally **or** throws a **checked exception** (a subclass of `java.lang.Exception` that is *not* a `RuntimeException`). This is implemented in `DefaultTransactionAttribute.rollbackOn(Throwable ex)`, which returns `ex instanceof RuntimeException || ex instanceof Error`. ### Terminology (define everything) - **Checked exception**: a `Throwable` the compiler forces you to declare or catch — e.g. `IOException`, `SQLException`, or your own `class InsufficientFundsException extends Exception`. - **Unchecked exception**: `RuntimeException` and its subclasses (e.g. `IllegalArgumentException`, `NullPointerException`, Spring's `DataAccessException`) — no compiler enforcement. - **Error**: severe conditions like `OutOfMemoryError`; also triggers rollback by default. ### Why this default? It comes from the **EJB** convention: checked ("application") exceptions were considered *recoverable business outcomes* the caller might handle without abandoning the unit of work, so the container committed; runtime ("system") exceptions signalled a genuine failure, so it rolled back. Spring kept the convention for familiarity. Notably, **all of Spring's own `DataAccessException` hierarchy is unchecked**, so real database failures always trigger rollback by default. ### The most common gotcha A developer writes `class OrderException extends Exception` (checked), throws it mid-transaction after a partial update, and is shocked the partial update is **committed**. The fix is `@Transactional(rollbackFor = OrderException.class)` — or simply extend `RuntimeException` for domain exceptions. ### The exception must escape the method The rollback rule is only evaluated for a `Throwable` that **propagates out of** the transactional method's proxy boundary. If you `try/catch` the exception inside the method and return normally, Spring sees a normal return and commits. If you catch it, do cleanup, and rethrow, the rethrown type is what's evaluated. ### One-line summary > Default = rollback on unchecked (`RuntimeException`) + `Error`, commit on checked. Override with `rollbackFor` / `noRollbackFor`.

  • If I catch the RuntimeException inside the @Transactional method and don't rethrow, does Spring roll back?
    No. The rollback rule is only applied to an exception that propagates out of the transactional method. A swallowed exception means a normal return, so Spring commits. (You could still force it with TransactionAspectSupport.currentTransactionStatus().setRollbackOnly().)
  • Are Spring's own data-access exceptions checked or unchecked?
    Unchecked — the entire org.springframework.dao.DataAccessException hierarchy extends RuntimeException, so genuine DB errors always trigger rollback under the default rule.

saying these in an interview costs you the question

  • Claiming Spring rolls back on ALL exceptions by default
  • Claiming checked exceptions roll back by default
  • Thinking a caught-and-swallowed exception still rolls back automatically
  • Believing you must declare rollbackFor to get rollback on NullPointerException

context

open as a page

How do rollbackFor and noRollbackFor change the default rollback behavior, and when would you use each?

level: middleimportance: must knowfreq 72%

basics

~20 s

rollbackFor adds exception types (including checked ones) that should trigger rollback. noRollbackFor names exceptions that should NOT roll back even though they normally would (e.g. a RuntimeException you treat as a business outcome). Both go on @Transactional.

open as a page

How does RuleBasedTransactionAttribute decide commit vs rollback when multiple rollbackFor/noRollbackFor rules could match the thrown exception?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Spring stores the rules in a RuleBasedTransactionAttribute. For a thrown exception it picks the rule whose type is the CLOSEST match in the exception's class hierarchy (smallest depth). If that winning rule is a noRollbackFor rule it commits; if rollbackFor, it rolls back. If no rule matches, it uses the default (rollback on RuntimeException/Error).

open as a page

You're setting a project-wide policy for transaction rollback rules. What trade-offs, pitfalls, and interactions with propagation would you weigh?

level: principalimportance: should knowfreq 30%

basics

~20 s

Decide whether all exceptions should roll back (rollbackFor = Exception.class) or keep the default. Watch for: swallowed exceptions bypassing rules, self-invocation not being proxied, and inner rollbacks marking the whole physical transaction rollback-only, causing UnexpectedRollbackException in outer methods.

open as a page

What are the semantics and risks of rollbackForClassName / noRollbackForClassName string matching?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

They match by exception class-name substring, not exact type. Spring walks up the exception's superclasses and matches if a class name CONTAINS the given string. That makes short patterns like "Exception" match almost everything, so use specific patterns.

open as a page