skip to content

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

level: seniorimportance: nice to knowfreq 30%

answer

  1. SUPPORTS = behavior depends on caller
  2. no tx branch = no rollback / auto-commit
  3. isolation on SUPPORTS is unreliable
  4. read-only attribute ignored when non-tx
  5. prefer REQUIRED for guaranteed atomicity

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.

solid answer

~50 s

SUPPORTS is context-dependent, which is its main hazard: the same method behaves differently depending on whether a caller opened a transaction. When one is active it joins it and gets full transactional semantics; when none is active it runs non-transactionally — no atomicity or rollback, isolation is whatever the JDBC connection/driver default is (often auto-commit), and the isolation and read-only attributes you set on @Transactional are essentially meaningless because there is no transaction to apply them to. If such a method issues multiple statements expecting them to be atomic, they won't be when run standalone. Spring's docs even warn that defining isolation on SUPPORTS is unreliable for exactly this reason. Because of this ambiguity, SUPPORTS is rarely the right choice for write logic; it fits read-only or metrics/logging code that is genuinely fine either way. When in doubt, prefer REQUIRED for a guaranteed boundary.

code

java · 14 lines
java
@Service
public class MetricsService {

    // isolation here is UNRELIABLE: it only applies if a caller's
    // transaction happens to be active; standalone this runs non-tx.
    @Transactional(propagation = Propagation.SUPPORTS,
                   isolation = Isolation.REPEATABLE_READ)
    public void recordHit(String key) {
        counterRepo.increment(key); // atomic only when joined to a tx
    }
}
// Called inside a @Transactional service -> joins, honors the tx.
// Called directly from a scheduler with no tx -> runs non-transactionally,
// isolation setting effectively ignored, each write auto-commits.

go deeper

for a junior

Know SUPPORTS runs without a transaction when none is active.

for a middle

Recognize there's no rollback in the non-tx branch and behavior depends on the caller.

for a senior

Explain why isolation/read-only are unreliable on SUPPORTS and when it's an acceptable choice.

for a principal

Weigh SUPPORTS' caller-dependent semantics against explicit REQUIRED/NOT_SUPPORTED for predictable transactional design.

## What SUPPORTS does **SUPPORTS** = `@Transactional(propagation = Propagation.SUPPORTS)`: join an existing transaction if present, otherwise run **non-transactionally**. The risks all stem from that second branch. ## The risks of the non-transactional branch - **1. Non-deterministic semantics by caller.** The exact same method is atomic when invoked inside a transaction and non-atomic when invoked standalone. That makes reasoning about correctness harder — a bug may appear only when the method is called from a non-transactional path. - **2. No rollback / no atomicity when standalone.** Without an active transaction there is nothing to roll back. Multiple `save`/`update` statements can partially apply and an exception won't undo prior ones. Any assumption of all-or-nothing is false in that branch. - **3. Isolation & read-only attributes are effectively ignored.** `@Transactional(isolation = ...)` and `readOnly = true` only take effect when a real transaction is created/joined. On the non-tx SUPPORTS branch there is no transaction to carry them, so they don't apply. The Spring reference documentation explicitly cautions that setting the **isolation level on SUPPORTS** leads to unpredictable results, because whether it takes effect depends on whether a transaction happens to be active. (Some managers even reject isolation on non-tx-creating propagations.) - **4. Connection / auto-commit behavior.** In the non-tx branch, data access typically runs with the connection's default (often `autoCommit=true`), meaning each statement commits immediately — the opposite of transactional grouping. - **5. Lazy loading / synchronization.** With JPA, the persistence context and transaction synchronization behave differently between the two branches, which can surface as `LazyInitializationException` or unexpected flush timing when the method runs without a transaction. ## Mechanism As with all propagation, the interceptor inspects the thread-bound transaction state (`TransactionSynchronizationManager`) via the AOP proxy; self-invocation bypasses it. ## When SUPPORTS is appropriate Genuinely optional-transaction work — read-only queries, metrics/telemetry writes to a non-critical store, logging — where correctness does not depend on atomicity. - For anything that must be atomic, use **REQUIRED** (or MANDATORY to force the caller to own the boundary). - If you specifically want to run outside a transaction on purpose, NOT_SUPPORTED/NEVER express that intent more clearly than relying on SUPPORTS' 'maybe' behavior.

  • Why does the Spring documentation warn against setting an isolation level on SUPPORTS?
    Because SUPPORTS may run without a transaction, and isolation only applies when a transaction exists; whether your setting takes effect depends on the caller, giving unpredictable results.

saying these in an interview costs you the question

  • Assuming a SUPPORTS method is always atomic
  • Believing isolation/read-only always apply on SUPPORTS
  • Choosing SUPPORTS for write logic that needs rollback

context