Spring's default declarative transaction rule rolls back on unchecked failures, while a checked exception requires an explicit rollback rule if its writes must be undone.
Spring transaction rollback: checked failures commit unless a rule says otherwise
Three calls, three outcomes
A proxied service inserts into an H2 table and throws. The unchecked failure rolls back, so the row count stays zero. A checked IOException without a rollback rule commits, even though the caller sees an exception. A second checked failure with rollbackFor = IOException.class leaves the count unchanged at one.
That default surprises import code that throws a checked validation or I/O exception after writing. Keep the transaction boundary around the full unit of work and name the exception classes that should roll back. Proxy traversal matters: a direct self-call can bypass the annotation.
A rollback is local to one data source
The test uses a local H2 DataSourceTransactionManager. It does not make file publication, broker messages or remote HTTP calls atomic with the database. After-commit events also need a durable delivery design before they can support reliable external work.
Checked source
@Transactional
public void checkedFailure() throws IOException {
jdbc.update("insert into receipt_import (receipt_id) values (?)", "R-checked");
throw new IOException("reject import");
}
@Transactional(rollbackFor = IOException.class)
public void checkedFailureWithRule() throws IOException {
jdbc.update("insert into receipt_import (receipt_id) values (?)", "R-explicit");
throw new IOException("reject import");
}Verification boundary
CheckedRollbackRuleTest.checkedExceptionCommitsByDefaultUnlessRollbackRuleIsExplicit runs in the downloadable Spring source kit. The excerpt omits imports and test setup; the kit contains the complete source.
Costs and boundaries
One H2 transaction manager and one proxied service establish only the stated local rollback behavior. Target-database isolation, deadlocks, migration interactions and external side effects need separate integration tests.
Common Mistakes
- Do not assume every exception triggers rollback by default.
- Do not call an annotated method directly through this and expect proxy advice.
- Do not claim that database rollback retracts an email or broker publish.
Read next
Spring TransactionTemplate: roll back a failed multi-row change, Spring transactional methods: call paths and rollback assumptions, Spring transaction events: run a listener after commit without claiming durability, Java exceptions: recovery boundaries and failure context.
