skip to content

What does Propagation.NESTED do in Spring transaction management, and how is it different from a completely separate transaction?

level: juniorimportance: should knowfreq 45%

answer

  1. savepoint = checkpoint inside one tx
  2. one physical transaction, not two
  3. outer rollback still wipes nested
  4. needs DataSourceTransactionManager
  5. no existing tx => acts like REQUIRED

basics

~10 s

NESTED runs inside the existing transaction but sets a savepoint first. If the nested part fails, Spring rolls back only to that savepoint, keeping the outer work. It is one physical transaction, not two.

solid answer

~40 s

Propagation.NESTED means: if a transaction already exists, execute the inner method within it but mark a JDBC savepoint at the start. If the inner method throws, Spring rolls back only to that savepoint, so the outer transaction's earlier work survives and it can still commit. It is a single physical transaction with an internal checkpoint — not a second, independent transaction. Because there is one physical transaction, the outer and inner share the same database connection and commit atomically at the very end; the outer rollback still discards everything. This is unlike REQUIRES_NEW, which suspends the outer and opens a genuinely separate physical transaction that commits independently. If no transaction exists when NESTED is reached, it behaves like REQUIRED and simply starts a new one.

code

java · 30 lines
java
@Service
public class ImportService {

    private final RowService rowService;

    public ImportService(RowService rowService) {
        this.rowService = rowService;
    }

    @Transactional // outer physical transaction
    public void importAll(List<Row> rows) {
        for (Row row : rows) {
            try {
                rowService.saveOne(row); // NESTED: savepoint per row
            } catch (RuntimeException ex) {
                // rolled back only to this row's savepoint;
                // previously saved rows survive, outer tx continues
                log.warn("Skipped bad row {}", row.id());
            }
        }
    }
}

@Service
class RowService {
    @Transactional(propagation = Propagation.NESTED)
    public void saveOne(Row row) {
        // insert; may throw on a constraint violation
    }
}

go deeper

for a junior

Know the one-line idea: NESTED = inner block with a checkpoint (savepoint) so it can be undone without killing the whole transaction.

for a middle

Should articulate 'one physical transaction' and that the outer rollback still discards nested work.

for a senior

Should name DataSourceTransactionManager as the requirement and contrast crisply with REQUIRES_NEW.

for a principal

Should discuss savepoint locking behavior, the JPA limitation, and when partial rollback is genuinely the right design vs. batching/idempotency alternatives.

## The problem it solves Sometimes you want part of a transaction to be able to fail and be undone without killing the whole transaction. Example: importing 100 rows where one bad row should be skipped, not abort the batch. `Propagation.NESTED` gives you a partial rollback point. ## What a savepoint is A **savepoint** is a named marker inside a database transaction. In JDBC it is `Connection.setSavepoint()`, which returns a `java.sql.Savepoint`. You can later call `Connection.rollback(savepoint)` to undo everything done *after* the savepoint while keeping everything done *before* it, and the transaction stays open. `Connection.releaseSavepoint(savepoint)` discards the marker on success. The whole transaction still commits or rolls back as a unit at the end — the savepoint only affects what is inside it. ## How NESTED uses savepoints When a Spring method annotated `@Transactional(propagation = Propagation.NESTED)` is entered **while an outer transaction already exists**, Spring does NOT open a new physical transaction. Instead the `PlatformTransactionManager` calls `setSavepoint()` on the existing connection. If the nested method completes normally, the savepoint is released. If it throws a rollback-triggering exception, Spring rolls back to that savepoint only, and the outer transaction continues and can still commit. There is **one physical transaction** and **one database connection** throughout. If **no** transaction exists yet when NESTED is reached, there is nothing to set a savepoint in, so NESTED simply starts a fresh transaction — behaving exactly like `REQUIRED`. ## Requirement: a savepoint-capable transaction manager NESTED only works with a `PlatformTransactionManager` whose `useSavepointForNestedTransaction()` returns true and whose transaction object implements `SavepointManager`. `org.springframework.jdbc.datasource.DataSourceTransactionManager` (and `JdbcTransactionManager`) support this — they build on plain JDBC connections. **`JpaTransactionManager` does NOT support savepoints by default**, so using NESTED with JPA/Hibernate typically throws `NestedTransactionNotSupportedException` ("JpaDialect does not support savepoints - check your JPA provider's capabilities"). Some JPA setups can be coaxed via `setNestedTransactionAllowed(true)` if the underlying dialect supports it, but the common, reliable case is JDBC-based managers. `JtaTransactionManager` likewise does not support savepoints. ## Contrast with REQUIRES_NEW `Propagation.REQUIRES_NEW` **suspends** the current transaction and opens a genuinely **separate physical transaction** with (usually) its own connection. That inner transaction commits or rolls back **independently** of the outer one — its committed work survives even if the outer later rolls back. NESTED is the opposite: everything is still one physical transaction, so an outer rollback wipes the nested (already-"rolled-forward") work too; the nested savepoint only protects the outer from the *inner's* failure, not the other way around. | Aspect | NESTED | REQUIRES_NEW | |---|---|---| | Physical transactions | one | two (outer suspended) | | Connection | shared | separate | | Inner rollback affects outer? | no (only to savepoint) | no | | Outer rollback affects inner? | **yes** (all undone) | no (inner already committed) | | Mechanism | JDBC savepoint | suspend/resume | | Works with JpaTransactionManager? | no (by default) | yes | ## Gotchas - **Self-invocation**: like all Spring `@Transactional`, propagation only applies when the call goes through the proxy. Calling a NESTED method from another method in the same bean bypasses AOP and the savepoint is never set. - **JPA/Hibernate + NESTED = exception** in the default config; this surprises people who assume propagation is transaction-manager-agnostic. - Savepoints hold locks acquired before them; rolling back to a savepoint releases locks taken after it but keeps the rest — long savepoint-heavy transactions can still hold locks a long time. - The nested method needs its exception to actually reach Spring to trigger the savepoint rollback; if you catch it inside, no rollback happens.

  • What happens if NESTED is reached and there is no existing transaction?
    There is no transaction to set a savepoint in, so Spring just starts a brand-new transaction — NESTED then behaves identically to REQUIRED.

saying these in an interview costs you the question

  • Saying NESTED starts a second, independent transaction (it does not — that's REQUIRES_NEW)
  • Claiming an outer rollback preserves the nested work (it does not; only inner-to-savepoint rollback is partial)
  • Assuming NESTED works out of the box with JPA/Hibernate

context