skip to content

How do you use Spring's SavepointManager API (createSavepoint / rollbackToSavepoint / releaseSavepoint) for nested control inside a transaction?

level: seniorimportance: should knowfreq 30%

answer

  1. TransactionStatus implements SavepointManager
  2. create -> opaque handle; rollbackTo -> undo after; release -> drop marker
  3. backs PROPAGATION_NESTED
  4. JDBC Connection.setSavepoint under the hood
  5. NestedTransactionNotSupportedException if unsupported

basics

~10 s

TransactionStatus implements SavepointManager. You call createSavepoint() to mark a point, do risky work, then rollbackToSavepoint(sp) to undo just that work (keeping earlier changes), or releaseSavepoint(sp) to discard the marker if things went fine.

solid answer

~40 s

Because TransactionStatus extends SavepointManager, within one physical transaction you can create partial rollback points. createSavepoint() returns an opaque Object handle for the current point. If a sub-operation fails, rollbackToSavepoint(handle) undoes everything done after the savepoint while preserving work done before it and keeping the outer transaction alive. releaseSavepoint(handle) drops the savepoint when you no longer need it (the changes remain part of the transaction). This maps directly to JDBC Connection.setSavepoint/rollback/releaseSavepoint under the hood. Spring uses exactly this mechanism to implement PROPAGATION_NESTED — you rarely call the API by hand. The main gotchas: savepoints need driver/DB support (most RDBMS yes; some managers throw NestedTransactionNotSupportedException), a savepoint is only valid within its own physical transaction, and a full rollback of the whole transaction invalidates all savepoints.

code

java · 18 lines
java
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
try {
    ledger.recordHeader(batchId);           // keep this no matter what
    for (Item item : items) {
        Object savepoint = status.createSavepoint();
        try {
            ledger.applyItem(item);         // risky per-item work
            status.releaseSavepoint(savepoint); // ok -> drop the marker, keep changes
        } catch (BusinessException ex) {
            status.rollbackToSavepoint(savepoint); // undo just this item
            ledger.recordSkipped(item, ex);        // outer tx still alive
        }
    }
    txManager.commit(status);
} catch (RuntimeException ex) {
    txManager.rollback(status);
    throw ex;
}

go deeper

for a junior

Know the three method names and that a savepoint is a partial-rollback marker.

for a middle

Explain create/rollbackTo/release semantics and that it backs @Transactional(NESTED).

for a senior

Contrast NESTED savepoints with REQUIRES_NEW on durability and connection usage; know the JDBC delegation.

for a principal

Decide savepoint vs new-transaction strategy for batch/partial-failure designs and account for manager/driver support and ORM flush timing.

**What a savepoint is.** A *savepoint* is a named marker inside a single database transaction that you can later roll back to, undoing only the work done *after* the marker while keeping earlier work and the transaction itself still open. It's a *partial* rollback within one physical transaction — not a nested independent transaction. **The SavepointManager interface.** Spring exposes savepoints through `org.springframework.transaction.SavepointManager`, which `TransactionStatus` extends. Three methods: - `Object createSavepoint()` — creates a savepoint and returns an **opaque handle** (you don't interpret it; just keep it). Throws `NestedTransactionNotSupportedException` if savepoints aren't supported. - `void rollbackToSavepoint(Object savepoint)` — rolls the transaction back to that savepoint, discarding changes made after it. The transaction stays active; the savepoint typically remains usable depending on the DB, but Spring's contract lets you continue. - `void releaseSavepoint(Object savepoint)` — releases/destroys the savepoint (frees resources). The data changes are **kept**; you're only saying 'I no longer need this marker.' Releasing is best-effort — some drivers ignore or don't support it, and Spring swallows benign failures. **Under the hood.** The JDBC implementation (`JdbcTransactionObjectSupport` / the DataSource transaction object) delegates to `java.sql.Connection.setSavepoint()`, `Connection.rollback(Savepoint)`, and `Connection.releaseSavepoint(Savepoint)`. So savepoint capability ultimately depends on the JDBC driver and database. **Relationship to PROPAGATION_NESTED.** You almost never call `createSavepoint()` yourself. Spring's `AbstractPlatformTransactionManager` uses this API to implement `PROPAGATION_NESTED`: entering a NESTED scope creates a savepoint on the *existing* physical transaction; if the nested scope fails, Spring rolls back to that savepoint (inner work undone) while the outer transaction can still commit; if the nested scope succeeds, the savepoint is released and its changes commit with the outer transaction. `TransactionStatus.hasSavepoint()` reports whether the current status is savepoint-backed. **Savepoint vs REQUIRES_NEW.** Both isolate inner failures, but differently: NESTED/savepoints stay within *one* physical transaction and *one* connection — the inner changes only become durable when the *outer* commits, and a rollback of the outer discards them too. REQUIRES_NEW opens a *separate* physical transaction (suspends the outer, uses another connection) that commits independently — its committed changes survive even if the outer later rolls back. **Edge cases & gotchas:** - **Support required.** Not all `PlatformTransactionManager`s support savepoints. `DataSourceTransactionManager` and JPA's `JpaTransactionManager` generally do (with a capable driver); `JtaTransactionManager` typically does **not** unless the JTA provider supports it — you get `NestedTransactionNotSupportedException`. Enable/allow via `setNestedTransactionAllowed(true)` where applicable. - **Scope.** A savepoint is only valid within the *same* physical transaction that created it. It cannot bridge a `REQUIRES_NEW` suspension. - **Full rollback invalidates savepoints.** Rolling back the whole transaction discards all savepoints. - **JPA/Hibernate flush timing.** With an ORM, pending changes may need flushing before a savepoint captures the intended state; unflushed changes and savepoint boundaries can interact confusingly. - **Don't leak handles.** Keep the returned handle to roll back or release; losing it means you can't target that savepoint. **When to use directly.** Prefer `@Transactional(propagation = NESTED)` for declarative nested control. Reach for the raw `SavepointManager` API only in low-level, programmatic flows (e.g., batch processing where you want per-item partial rollback within one big transaction).

  • How is a savepoint (NESTED) different from PROPAGATION_REQUIRES_NEW for isolating an inner failure?
    NESTED stays inside one physical transaction/connection — inner changes only become durable when the outer commits, and an outer rollback discards them. REQUIRES_NEW is a separate physical transaction that commits independently and survives an outer rollback.
  • What happens if the transaction manager or driver doesn't support savepoints?
    createSavepoint() throws NestedTransactionNotSupportedException. This is common with plain JTA managers; DataSourceTransactionManager/JpaTransactionManager with a capable JDBC driver generally support them.

saying these in an interview costs you the question

  • Calling a savepoint a nested independent transaction (it shares the same physical transaction and connection)
  • Believing releaseSavepoint() discards the changes (it keeps them; it only drops the marker)
  • Assuming savepoint changes are durable before the outer transaction commits
  • Assuming every transaction manager supports savepoints

context