skip to content

What does TransactionStatus.isNewTransaction() tell you, and why does it matter with transaction propagation?

level: middleimportance: should knowfreq 40%

answer

  1. new (outer) vs participate (inner)
  2. only new/outermost really commits
  3. REQUIRED join -> isNew false
  4. REQUIRES_NEW -> always isNew true
  5. inner rollback => marks shared tx rollback-only

basics

~10 s

isNewTransaction() returns true if this call actually started a brand-new physical transaction, and false if it joined an already-running one. It matters because only the outermost, new transaction actually commits or rolls back.

solid answer

~40 s

isNewTransaction() distinguishes a *new* physical transaction from *participation* in an existing one. With the default PROPAGATION_REQUIRED, if a transaction is already active the inner call joins it and getTransaction() returns a status with isNewTransaction() == false; the outermost caller that opened the transaction gets isNewTransaction() == true. This is decisive because Spring only performs the real commit/rollback for the new (outermost) transaction — an inner participant's commit() is effectively a no-op, and its rollback() typically just sets the shared transaction rollback-only. Knowing whether you're new tells you whether your commit/rollback truly controls the outcome, which is important in framework-y code, custom transaction handling, or when reasoning about why an inner rollback dooms the whole thing. PROPAGATION_REQUIRES_NEW always yields a new transaction (suspending the outer one), so isNewTransaction() is true.

code

java · 12 lines
java
TransactionStatus outer = txManager.getTransaction(
        new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED));
System.out.println(outer.isNewTransaction()); // true - started it

// Nested call with REQUIRED joins the same physical transaction
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus inner = txManager.getTransaction(def);
System.out.println(inner.isNewTransaction()); // false - participating

txManager.commit(inner); // effectively a no-op physically
txManager.commit(outer); // the real commit happens here

go deeper

for a junior

Grasp that new = started it, false = joined an existing one.

for a middle

Connect isNewTransaction to PROPAGATION_REQUIRED vs REQUIRES_NEW and who actually commits.

for a senior

Explain inner-rollback-marks-rollback-only and the resulting UnexpectedRollbackException.

for a principal

Reason about physical vs logical transactions across a call graph and pick propagation to isolate failures.

**Physical vs logical transactions.** Spring separates the *physical* transaction (the real database transaction / connection with its own commit boundary) from *logical* transaction scopes created by nested `@Transactional` / `getTransaction()` calls. With `PROPAGATION_REQUIRED` (the default), many logical scopes can *share one* physical transaction. **isNewTransaction().** `TransactionStatus.isNewTransaction()` returns `true` only when the current `getTransaction(...)` call **started a new physical transaction**. If a transaction was already in progress and this call merely **joined/participated**, it returns `false`. **Why it matters — only the new one really commits.** Spring drives the *actual* database commit or rollback only for the transaction that is new (the outermost one). When an inner participant calls `commit()`, Spring sees `isNewTransaction() == false` and does essentially nothing to the physical transaction — the real commit is deferred to the outer scope. When an inner participant calls `rollback()`, Spring typically does not physically roll back immediately; instead it marks the shared transaction **rollback-only** (see `setRollbackOnly()`), so the eventual outer commit is forced to roll back and throws `UnexpectedRollbackException`. **Propagation interactions:** - `PROPAGATION_REQUIRED`: join if present, else create. Outer = new (`true`), inner = participate (`false`). - `PROPAGATION_REQUIRES_NEW`: always suspend any existing transaction and start a fresh, independent physical transaction — `isNewTransaction()` is `true`, and its commit/rollback is independent of the suspended outer one. - `PROPAGATION_NESTED`: uses a **savepoint** within the *existing* physical transaction rather than a new one; the status is not a 'new transaction' in the full sense but a savepoint-scoped one (`hasSavepoint()` becomes relevant). - `PROPAGATION_SUPPORTS` / `NOT_SUPPORTED` / `NEVER` / `MANDATORY`: govern whether a transaction is required, forbidden, or joined. **Where you use it.** Application code rarely branches on `isNewTransaction()`, but it's central for: understanding Spring internals (`AbstractPlatformTransactionManager`), writing custom transaction utilities, and diagnosing 'my inner method rolled back and now the whole outer thing failed with UnexpectedRollbackException.' It's also used in `TransactionSynchronization` callbacks to know if cleanup should run. **Gotcha.** Because an inner `REQUIRED` participant's `rollback()` only *marks* rather than *executes* a rollback, code that catches the inner exception and continues will still hit `UnexpectedRollbackException` at the outer commit. If you need the inner failure to be *isolatable*, use `REQUIRES_NEW` (independent commit) or `NESTED` (savepoint you can roll back to).

  • If an inner PROPAGATION_REQUIRED method calls rollback() but the outer catches nothing wrong, what does the outer commit do?
    The inner rollback marked the shared transaction rollback-only, so the outer commit cannot commit — Spring rolls back and throws UnexpectedRollbackException.
  • How would you make an inner failure roll back only the inner work, leaving the outer transaction committable?
    Use PROPAGATION_REQUIRES_NEW (independent physical transaction) or PROPAGATION_NESTED (a savepoint you can roll back to) instead of REQUIRED.

saying these in an interview costs you the question

  • Claiming every @Transactional call starts a new physical transaction (REQUIRED joins existing ones)
  • Thinking an inner commit with REQUIRED actually commits to the database
  • Assuming an inner rollback is isolated from the outer transaction under REQUIRED

context