skip to content

In Spring, what do the SUPPORTS, MANDATORY, and NEVER transaction propagation modes each do?

level: juniorimportance: must knowfreq 70%

answer

  1. SUPPORTS = join if present, else no tx
  2. MANDATORY = must have one, else throw
  3. NEVER = must NOT have one, else throw
  4. IllegalTransactionStateException
  5. checks via proxy / thread-bound state

basics

~20 s

SUPPORTS joins an existing transaction if one is active, otherwise runs without one. MANDATORY requires an active transaction and throws if none exists. NEVER requires that no transaction is active and throws if one exists.

solid answer

~40 s

These are three values of Spring's Propagation enum, set via @Transactional(propagation = ...). SUPPORTS is passive: if a transaction is already running the method joins it, otherwise it runs non-transactionally (no new transaction started). MANDATORY enforces that a caller already opened a transaction; if none is active Spring throws IllegalTransactionStateException before the method runs. NEVER is the opposite guard: the method must run with no active transaction, and if one exists Spring throws IllegalTransactionStateException. So SUPPORTS is tolerant of both cases, MANDATORY demands a transaction, and NEVER forbids one. The checks happen in the AOP interceptor around the method, driven by the current thread's transaction state.

code

java · 21 lines
java
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;

@Service
public class TxModes {

    // Joins an active tx; runs non-transactionally if none.
    @Transactional(propagation = Propagation.SUPPORTS)
    public void reportMetrics() { /* ... */ }

    // Requires the caller to have already started a tx.
    // Throws IllegalTransactionStateException if none is active.
    @Transactional(propagation = Propagation.MANDATORY)
    public void appendToLedger() { /* ... */ }

    // Requires that NO tx is active.
    // Throws IllegalTransactionStateException if one exists.
    @Transactional(propagation = Propagation.NEVER)
    public void callExternalNonTxSystem() { /* ... */ }
}

go deeper

for a junior

Know the one-line behavior of each of the three and that MANDATORY/NEVER throw.

for a middle

Name the exact exception (IllegalTransactionStateException) and contrast SUPPORTS with REQUIRED.

for a senior

Explain the proxy/thread-bound mechanism and self-invocation caveat; note SUPPORTS-non-tx isolation gotcha.

for a principal

Position these as contract-enforcement tools in a layered design and reason about when validation-only propagation is preferable to auto-creating transactions.

**Propagation** controls how a `@Transactional` method behaves relative to any transaction that is already running on the current thread. Spring exposes it through the enum `org.springframework.transaction.annotation.Propagation`, used as `@Transactional(propagation = Propagation.SUPPORTS)`. The decision is made by the transaction interceptor (`TransactionInterceptor`) that Spring's AOP proxy wraps around your bean method, using the `PlatformTransactionManager`. **SUPPORTS** — 'use a transaction if there is one, but don't require it.' If a transaction is already active when the method is entered, the method **joins** it (participates in the same physical transaction and connection). If **no** transaction is active, the method runs **non-transactionally** — Spring does not start one. This is the most passive mode. A subtle gotcha: when SUPPORTS runs without a transaction, there is no transactional context, so isolation level is whatever the connection/driver default is, auto-commit may be in effect, and if you later nest a transaction the earlier reads were not part of it. **MANDATORY** — 'there MUST already be a transaction; I will not start one.' If a transaction is active, the method joins it. If none is active, Spring throws `org.springframework.transaction.IllegalTransactionStateException` with a message like *"No existing transaction found for transaction marked with propagation 'mandatory'"*. The method body never executes in that case. Use it to assert that a component may only be called from within an existing transactional boundary — e.g. a low-level helper that must never open its own transaction. **NEVER** — 'there must be NO transaction.' If no transaction is active, the method runs non-transactionally (fine). If a transaction **is** active, Spring throws `IllegalTransactionStateException` (*"Existing transaction found for transaction marked with propagation 'never'"*). Use it to guard code that must not run inside a transaction — for example an operation that does long-running or non-transactional work you never want to hold a DB transaction open for. **How the 'current transaction' is known:** Spring binds transaction state to the thread via `TransactionSynchronizationManager`. All three modes inspect that thread-bound state. Because the check is done by the proxy, it only fires on calls that actually go through the proxy — a **self-invocation** (one method in a bean calling another `this.method()`) bypasses the proxy, so the propagation setting on the inner method is ignored. This is the classic reason a MANDATORY/NEVER check appears not to trigger. **Contrast with the defaults you already know:** REQUIRED (the default) joins an existing transaction or **starts a new one** if none exists — unlike SUPPORTS which won't start one. REQUIRES_NEW always suspends any existing transaction and starts a fresh one. NOT_SUPPORTED suspends any existing transaction and runs non-transactionally — similar to NEVER's *runtime* behavior when a tx exists, except NOT_SUPPORTED tolerates and suspends it whereas NEVER throws. **Edge cases / gotchas:** - MANDATORY and NEVER *validate*; they never create or suspend anything. - The exception is a runtime `IllegalTransactionStateException`, thrown before your code runs, so it isn't something your method body can catch by itself. - SUPPORTS + no active tx still applies transaction *synchronization* only if a tx is present; without one, `@Transactional` gives you essentially nothing (no rollback semantics). - Read-only flags and isolation on a SUPPORTS method that runs non-transactionally are effectively ignored.

  • What exception does MANDATORY throw when no transaction is active, and when is it thrown?
    org.springframework.transaction.IllegalTransactionStateException, thrown by the transaction interceptor before the method body executes — 'No existing transaction found for transaction marked with propagation mandatory'.
  • How does SUPPORTS differ from REQUIRED?
    REQUIRED (the default) starts a new transaction if none is active; SUPPORTS never starts one — it only joins an existing transaction and otherwise runs non-transactionally.

saying these in an interview costs you the question

  • Saying SUPPORTS starts a new transaction when none exists (that's REQUIRED)
  • Saying MANDATORY creates a transaction (it only validates and joins)
  • Confusing NEVER with NOT_SUPPORTED (NEVER throws; NOT_SUPPORTED suspends and continues)

context