A nested transaction can roll back to a savepoint inside one physical JDBC transaction while the surrounding transaction continues.
Spring nested transactions: roll back one step to a JDBC savepoint
The outer write still has one final commit
The H2 fixture inserts accept, enters a nested step that inserts temporary-check and fails, then inserts record-rejection in the outer transaction. The result contains accept and record-rejection only. The nested failure is caught after Spring rolls back to its savepoint; it has not committed the outer work.
This behavior needs a transaction manager and database path that support savepoints. The fixture explicitly enables nested transactions on a DataSourceTransactionManager. A second database, an HTTP call or a message broker cannot be rewound by that savepoint. The outer transaction lesson explains the overall commit boundary.
Do not swallow every failure
The catch is deliberate for a rejected optional step. If the nested failure means the entire command is invalid, propagate it so the outer transaction rolls back. If the outer write fails after the catch, all surviving rows still roll back together. A savepoint is a scoped rollback tool, not a replacement for input validation.
Checked source
outer.executeWithoutResult(status -> {
jdbc.update("insert into receipt_step values (?)", "accept");
assertThrows(IllegalStateException.class, () -> nested.executeWithoutResult(child -> {
jdbc.update("insert into receipt_step values (?)", "temporary-check");
throw new IllegalStateException("reject nested step");
}));
jdbc.update("insert into receipt_step values (?)", "record-rejection");
});Verification boundary
NestedSavepointContractTest.nestedFailureRollsBackToSavepointWhileOuterWorkCommits runs in the downloadable Spring source kit. The excerpt omits imports and surrounding setup; the kit contains the complete tests.
Costs and limits
The fixture makes three small SQL writes and one savepoint. Its elapsed cost depends on the database and logging path; no throughput claim is made. The H2 result does not prove behavior on another driver or with JPA flush timing.
Common Mistakes
- Do not call a savepoint an independent commit.
- Do not assume every transaction manager supports nested propagation.
- Do not catch a failure and continue when the outer command should fail.
Read next
Spring transaction propagation: joined rollback and independent commit, Spring TransactionTemplate: roll back a failed multi-row change, Spring NESTED versus REQUIRES_NEW: savepoint or independent commit.
