Foundation · Build

Propagation & Isolation in Practice

Propagation decides whether a method joins the caller’s transaction or starts its own; isolation decides which concurrent anomalies remain visible.

Primary source: Spring Framework 6.2: Transaction Propagation and Isolation

30 min

Lesson 14 of 17

1d streak

The Architectural Question

Default propagation joins whatever transaction is active: one failed payment silently discards every audit row, and raising isolation globally serializes workloads that never needed it.

What this guide proves

  • Choose REQUIRED, REQUIRES_NEW, and SUPPORTED deliberately
  • Explain read phenomena from READ_UNCOMMITTED to SERIALIZABLE
  • Keep audit writes alive when the business transaction rolls back

Transaction propagation: REQUIRED vs REQUIRES_NEW

Propagation defines how transactions behave when one transactional service calls another. Choosing the wrong propagation creates unexpected rollbacks or locks.

PropagationIf Transaction ExistsIf No Transaction ExistsCommon Use Case
REQUIRED (Default)Joins current transactionCreates a new transactionStandard business services
REQUIRES_NEWSuspends current transaction; creates independent new oneCreates a new transactionAudit logging, billing attempts that must persist even if parent rolls back
MANDATORYJoins current transactionThrows IllegalTransactionStateExceptionMethods that must never run without an active transaction
SUPPORTSJoins current transactionRuns non-transactionallyRead queries that can adapt to caller context

Database isolation levels and concurrency phenomena

Isolation LevelDirty Reads Allowed?Non-Repeatable Reads Allowed?Phantom Reads Allowed?
READ_UNCOMMITTEDYESYESYES
READ_COMMITTED (PostgreSQL/Oracle Default)NOYESYES
REPEATABLE_READ (MySQL InnoDB Default)NONOYES (MVCC prevents in most modern DBs)
SERIALIZABLENONONO (highest isolation, highest lock contention)

Active Recall Checkpoint

What happens if a REQUIRES_NEW method throws an exception that is caught and swallowed by the outer REQUIRED caller?

Hint: Are the two transactions independent?

Show answer & explanation

The inner REQUIRES_NEW transaction rolls back. Because the outer method caught the exception, the outer transaction can still commit its own changes successfully.

Propagation rules of thumb

Use it when

Default to Propagation.REQUIRED for almost all business workflows. Use REQUIRES_NEW strictly for independent audit logs or dead-letter error records.

Reconsider when

Avoid changing transaction isolation levels on individual methods unless resolving a documented concurrency race condition.

Runtime Architecture & Flow

High-value visual model

Propagation and Isolation Decision Flow

Propagation decides whether code joins, suspends, or creates a transaction. Isolation decides what concurrent data changes can be observed.

1. REQUIRED joins the use case

Most service methods should share the existing transaction boundary.

2. REQUIRES_NEW suspends the caller

Use only when nested work must commit independently, such as audit logging.

3. NESTED uses a savepoint when supported

It can roll back part of the work without abandoning the outer transaction.

4. Isolation controls concurrent visibility

Test dirty reads, non-repeatable reads, phantoms, and write skew against the target database.

Transaction decision tree: Pick propagation from business consistency boundaries; pick isolation from the anomaly you must prevent.

Working Implementation

Code Walkthrough

Make an audit service write with REQUIRES_NEW so a declined payment still records who attempted it.

OrderAuditService.java
@Service
class OrderService {
    private final AuditService audit;

    @Transactional
    void placeOrder(PlaceOrder command) {
        orders.save(Order.from(command));
        audit.record(command.requestId());
        throw new PaymentDeclined();
    }
}

@Service
class AuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void record(UUID requestId) {
        auditEvents.save(new AuditEvent(requestId, Instant.now()));
    }
}
PaymentDeclined rolls the order back while the REQUIRES_NEW audit insert stays committed in its own transaction.
That split has to be a deliberate decision: a trail of attempted orders is worth keeping, a half-written order is not.
The call has to travel through the injected AuditService proxy; calling record() on this would join the caller transaction instead.

Synthesis & Production Verification

Deliberate Failure Condition

  1. Break: Call audit.record() inside placeOrder() where both use REQUIRED and the order save throws
  2. Observe: The audit trail vanishes exactly when you need it most
  3. Repair: REQUIRES_NEW suspends the outer transaction and commits independently—self-invocation bypasses this entirely because no proxy boundary is crossed.

Production Acceptance Criteria

  • Prove REQUIRED joins and REQUIRES_NEW suspends via SQL logs
  • Reproduce one read phenomenon at READ_COMMITTED
  • Trigger self-invocation and explain why propagation did not apply

Architectural & Technical Review

Core Interview Questions

Q01What is the difference between Propagation.REQUIRED and Propagation.REQUIRES_NEW?Show answer

REQUIRED joins the existing transaction if present; REQUIRES_NEW suspends the current transaction and starts an independent new one.

Q02What concurrency phenomena does READ_COMMITTED prevent?Show answer

It prevents dirty reads, but non-repeatable reads and phantom reads remain possible depending on database concurrency control.

Q03What occurs if an inner REQUIRES_NEW method fails and the exception is caught by the outer caller?Show answer

The inner transaction rolls back independently, while the outer transaction can still commit if the exception was handled.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Using REQUIRES_NEW without connection pool headroom — Each suspended transaction retains its physical connection while the new transaction borrows another, risking pool deadlocks.
  • Pitfall: Overriding isolation levels arbitrarily — Setting non-standard isolation levels increases database locking contention without measurable benefit for standard queries.

Primary Documentation & Specifications