skip to content

Explain the roles of TransactionDefinition and TransactionStatus in the PlatformTransactionManager contract. How do they differ?

level: seniorimportance: should knowfreq 40%

answer

  1. Definition = input spec (propagation/isolation/timeout/read-only)
  2. Status = output handle to the live transaction
  3. isNewTransaction() distinguishes create vs. join
  4. setRollbackOnly() lives on the status (runtime)
  5. Status implements SavepointManager → NESTED

basics

~10 s

TransactionDefinition is the input describing how the transaction should behave (propagation, isolation, timeout, read-only). TransactionStatus is the output handle representing the running transaction, letting you check if it's new and mark it rollback-only.

solid answer

~40 s

They sit on opposite sides of getTransaction. TransactionDefinition is an immutable specification you pass in: propagation behavior, isolation level, timeout, read-only hint, and an optional name — it says how the transaction should be created or joined. TransactionStatus is what comes back: a live handle to the resulting transaction. It reports isNewTransaction() (whether a physical transaction was actually started vs. joining an existing one, important for propagation like REQUIRED vs. NESTED), lets you setRollbackOnly() to force an eventual rollback, exposes isRollbackOnly()/isCompleted(), and manages savepoints (it implements SavepointManager). You commit or roll back by passing that same status back to the manager. So: definition = request/config, status = runtime state/handle.

code

java · 19 lines
java
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setTimeout(10);
def.setReadOnly(false);

TransactionStatus status = txManager.getTransaction(def);
try {
    if (status.isNewTransaction()) {
        // we actually started a physical transaction here
    }
    // ... work ...
    // Decide at runtime to force a rollback without throwing:
    if (someCondition) status.setRollbackOnly();
    txManager.commit(status); // if rollback-only, this rolls back + throws UnexpectedRollbackException
} catch (RuntimeException ex) {
    txManager.rollback(status);
    throw ex;
}

go deeper

for a junior

Know definition = input config, status = returned handle.

for a middle

List the definition properties and that status carries setRollbackOnly/isNewTransaction.

for a senior

Explain how isNewTransaction and rollback-only implement propagation and the UnexpectedRollbackException gotcha.

for a principal

Relate status.SavepointManager to NESTED propagation and reason about rollback-only poisoning across module boundaries.

## Two halves of one call `TransactionStatus getTransaction(TransactionDefinition definition)` takes a **definition** (what you want) and returns a **status** (what you got). Understanding the split clarifies how propagation, rollback-only, and savepoints work. ## TransactionDefinition — the *specification* (input) `org.springframework.transaction.TransactionDefinition` is a read-only description of the desired transaction. Its properties: - **Propagation** — how this call relates to an already-running transaction: `PROPAGATION_REQUIRED` (join or create — the default), `REQUIRES_NEW` (suspend the outer and start a fresh one), `NESTED` (a savepoint inside the outer), `SUPPORTS`, `MANDATORY`, `NOT_SUPPORTED`, `NEVER`. - **Isolation** — `ISOLATION_DEFAULT` or an explicit level (READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE…). - **Timeout** — seconds before the transaction is rolled back. - **Read-only** — a hint allowing the backend to optimize (e.g., Hibernate flush mode MANUAL). - **Name** — optional label, surfaced in monitoring. Common concrete types: `DefaultTransactionDefinition` (a mutable builder-style default), and `@Transactional`'s attributes are adapted into a `TransactionDefinition` (via `RuleBasedTransactionAttribute`). It is essentially *configuration*; it holds no live resources. ## TransactionStatus — the *handle* (output) `org.springframework.transaction.TransactionStatus` represents the transaction that `getTransaction` produced. Since Spring 5.2 it extends `TransactionExecution` (basic flags) and `SavepointManager`. Key capabilities: - **`isNewTransaction()`** — `true` if a new physical transaction was started, `false` if this call *joined* an existing one (e.g., inner `REQUIRED` method). The manager uses this internally: committing a participating (non-new) status is often a no-op deferred to the outermost transaction. - **`setRollbackOnly()` / `isRollbackOnly()`** — mark the transaction so it can *only* roll back. This is how an inner method poisons an outer one: when the outer commits, the manager sees rollback-only and throws `UnexpectedRollbackException` instead of committing. - **`isCompleted()`** — whether commit/rollback already ran. - **Savepoints** (`hasSavepoint()`, `createSavepoint()`, `rollbackToSavepoint()`, `releaseSavepoint()`) — the mechanism behind `PROPAGATION_NESTED`. ## Why the separation matters - **Propagation logic lives in the manager**, driven by the definition; the status merely reports the outcome (`isNewTransaction`). You cannot tell from the definition alone whether a real transaction was started — you read `status.isNewTransaction()`. - **Rollback-only is on the status, not the definition** — because it is a *runtime* decision made during execution. - **Commit can roll back**: `commit(status)` where the status is rollback-only rolls back and raises `UnexpectedRollbackException`. This surprises people who assume commit always commits. ## Gotcha Marking `setRollbackOnly()` deep in a call stack that participates in an outer transaction will roll back the *entire* outer transaction, and the outer caller sees `UnexpectedRollbackException` even though it didn't throw anything itself — a classic 'why did my transaction roll back' puzzle.

  • Why is setRollbackOnly on TransactionStatus rather than TransactionDefinition?
    Because it is a runtime decision made while the transaction executes, whereas the definition is a static specification fixed before the transaction starts. The status carries mutable, live state.
  • What does status.isNewTransaction() return for an inner @Transactional(REQUIRED) method?
    false — it joins the outer transaction rather than starting a new physical one, so the manager defers commit to the outermost boundary.

saying these in an interview costs you the question

  • Saying you set the isolation level on TransactionStatus — isolation is on the TransactionDefinition (input).
  • Assuming commit(status) always commits regardless of rollback-only state.
  • Thinking isNewTransaction() reflects the propagation setting directly rather than the actual outcome.

context