BackendBench
Coffee

Unravel โ€บ Java concepts โ€บ Spring and Boot

Spring

Transaction propagation and isolation

Propagation decides what happens when a transactional method calls another transactional method (join the current transaction or start a new one).

Learn these first

At a glance

Mental model

Propagation is whether a second person joins your shared document session or opens their own copy.

What happens, in order
  1. 1REQUIRED (default): join the caller transaction, or start one if none existsEverything commits or rolls back together.
  2. 2REQUIRES_NEW: suspend the caller transaction and run in a brand new one that commits on its ownUse it for audit logs that must survive a rollback of the main work.
  3. 3NESTED uses a savepoint, SUPPORTS and NOT_SUPPORTED run with or without a transaction, MANDATORY throws if there is none, NEVER throws if there is one
  4. 4Isolation levels from weakest to strongest: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLEStronger levels prevent anomalies (dirty reads, non-repeatable reads, phantoms) at the cost of more locking or retries.
Remember
Watch out
Catching an exception from a joined transaction and expecting the outer one to commitREQUIRES_NEW in a loop with a small connection poolBelieving the transaction follows work into @Async or parallel streams (it does not)Raising isolation to cure a race that a unique key would solve
Example (highlighted lines explain it)
@Servicepublic class AuditService {    @Transactional(propagation = Propagation.REQUIRES_NEW)    public void record(String action, long userId) {        auditRepository.save(new AuditEntry(action, userId));    }}@Servicepublic class TransferService {    @Transactional(isolation = Isolation.REPEATABLE_READ)    public void transfer(long from, long to, BigDecimal amount) {        accounts.debit(from, amount);        accounts.credit(to, amount);        audit.record("transfer", from);    }}
  1. line 3 uses Propagation, the core of Transaction propagation and isolation
  2. line 11 uses Isolation, the core of Transaction propagation and isolation
Read the full explanation (about 2 min)

Propagation decides what happens when a transactional method calls another transactional method (join the current transaction or start a new one). Isolation decides how much one transaction can see of another one in progress.

A mental model

Propagation is whether a second person joins your shared document session or opens their own copy. Isolation is how much of your unsaved edits colleagues are allowed to see while you work.

How it works

REQUIRED (default): join the caller transaction, or start one if none exists. Everything commits or rolls back together.

REQUIRES_NEW: suspend the caller transaction and run in a brand new one that commits on its own. Use it for audit logs that must survive a rollback of the main work.

NESTED uses a savepoint, SUPPORTS and NOT_SUPPORTED run with or without a transaction, MANDATORY throws if there is none, NEVER throws if there is one.

Isolation levels from weakest to strongest: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE. Stronger levels prevent anomalies (dirty reads, non-repeatable reads, phantoms) at the cost of more locking or retries.

Going deeper

With REQUIRED, an inner method that throws marks the shared transaction rollback-only. Even if the outer code catches the exception, the commit then fails with UnexpectedRollbackException. This surprises almost everyone once.

REQUIRES_NEW holds a second database connection while the first waits. Under load, with a small pool, that can deadlock the application: every thread holds one connection and waits for another.

Most databases default to READ_COMMITTED (PostgreSQL, Oracle) or REPEATABLE_READ (MySQL InnoDB). Raising isolation is rarely the right fix; optimistic locking or a unique constraint is usually simpler.

Under the hood

TransactionInterceptor asks the PlatformTransactionManager for a transaction using the definition. The connection is bound to the current thread (TransactionSynchronizationManager), which is why repositories called later in the same thread automatically share it, and why switching threads loses the transaction.

Common mistakes

  • Catching an exception from a joined transaction and expecting the outer one to commit.
  • REQUIRES_NEW in a loop with a small connection pool.
  • Believing the transaction follows work into @Async or parallel streams (it does not).
  • Raising isolation to cure a race that a unique key would solve.

How Unravel recognises it

When you paste code, Unravel looks for these signs of transaction propagation and isolation. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Frequently asked questions

What is Transaction propagation and isolation?

Propagation decides what happens when a transactional method calls another transactional method (join the current transaction or start a new one). Isolation decides how much one transaction can see of another one in progress.

What are the most common mistakes with Transaction propagation and isolation?

Catching an exception from a joined transaction and expecting the outer one to commit. REQUIRES_NEW in a loop with a small connection pool. Believing the transaction follows work into @Async or parallel streams (it does not). Raising isolation to cure a race that a unique key would solve.

How can I see Transaction propagation and isolation in real code?

Paste your own Java into Unravel and it will point out where transaction propagation and isolation appears, explain how it works at the detail level you choose, and draw the flow of the code around it.