skip to content

SUPPORTS / MANDATORY / NEVER

SUPPORTS runs either way, MANDATORY demands an existing transaction, and NEVER refuses to run inside one. They are the guardrails you reach for when a method must only ever be called in a specific transactional context.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

When would you use MANDATORY propagation, and what happens if the method is called outside a transaction?

level: middleimportance: should knowfreq 55%

basics

~10 s

Use MANDATORY when a method must only run inside an existing transaction opened by its caller. If called with no active transaction, Spring throws IllegalTransactionStateException and the method body never runs.

open as a page

What does NEVER propagation guarantee, and how does it differ from NOT_SUPPORTED?

level: seniorimportance: should knowfreq 40%

basics

~10 s

NEVER guarantees the method runs with no active transaction, throwing IllegalTransactionStateException if one exists. NOT_SUPPORTED also runs non-transactionally but tolerates an existing transaction by suspending it instead of throwing.

open as a page

What are the subtle risks of using SUPPORTS when no transaction is active?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

With SUPPORTS and no active transaction, the method runs without one, so there is no rollback, isolation is the connection default, and any read-only/isolation settings on the annotation are effectively ignored. Behavior differs depending on the caller.

open as a page

How would you use MANDATORY and NEVER to enforce transactional boundaries in a layered architecture, and what limits this enforcement?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Mark low-level operations MANDATORY so they must run inside a caller's transaction, and mark operations that must stay outside one NEVER. Both fail fast with IllegalTransactionStateException. The main limit: the checks only fire through the Spring proxy, so self-invocation bypasses them.

open as a page