Unravel โบ Java concepts โบ Spring and Boot
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
Propagation is whether a second person joins your shared document session or opens their own copy.
- 1REQUIRED (default): join the caller transaction, or start one if none existsEverything commits or rolls back together.
- 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.
- 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
- 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.
- With REQUIRED, an inner method that throws marks the shared transaction rollback-only
- REQUIRES_NEW holds a second database connection while the first waits
- TransactionInterceptor asks the PlatformTransactionManager for a transaction using the definition
@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); }}- line 3 uses Propagation, the core of Transaction propagation and isolation
- 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_NEWin a loop with a small connection pool.- Believing the transaction follows work into
@Asyncor 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:
- Types:
PropagationIsolationTransactionSynchronizationManagerTransactionSynchronizationTransactionStatusTransactionDefinitionUnexpectedRollbackExceptionDefaultTransactionDefinition - Method calls:
setRollbackOnly() - Constants:
Propagation.REQUIRES_NEWPropagation.REQUIREDPropagation.NESTEDPropagation.SUPPORTSPropagation.MANDATORYPropagation.NEVERPropagation.NOT_SUPPORTED
Part of a learning path
- JPA and Hibernate in depth ยท previous: Optimistic and pessimistic locking ยท next: JPA inheritance mapping
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.