1. What Is Transaction Propagation?
In Spring, when a method is annotated with @Transactional
, Spring manages the transaction automatically. But what happens when a method annotated with @Transactional
calls another transactional method?
Propagation defines how transactional behavior should behave in nested method calls — whether to continue an existing transaction, start a new one, or suspend the current one.
2. The 7 Propagation Types in Spring
Spring provides 7 propagation behaviors, defined in org.springframework.transaction.annotation.Propagation
.
Propagation Type | Description |
REQUIRED | Join current transaction or create a new one if none exists (default). |
REQUIRES_NEW | Always create a new transaction, suspending any existing one. |
NESTED | Executes within a nested transaction if one exists. Otherwise, new one. |
SUPPORTS | Join a transaction if available. Else, execute non-transactionally. |
NOT_SUPPORTED | Suspend current transaction and execute non-transactionally. |
NEVER | Throw exception if there is an existing transaction. |
MANDATORY | Must run within an existing transaction. Throws exception otherwise. |
3. Detailed Explanation and Code for Each Propagation Type
We’ll use two service classes: OuterService
and InnerService
.
3.1 Propagation.REQUIRED (Default)
Detailed Behavior
This is the default propagation type in Spring. If a transaction already exists in the current thread, the method will participate in that existing transaction. If no transaction exists, Spring will start a new one. This ensures that all operations within the current logical flow are part of a single cohesive transaction.
Transaction Flow
- Existing transaction? → Join it.
- No transaction? → Create a new one.
Real-World Use Case
You are building a UserService
where a method createUser()
saves both a user record and sends a welcome email. You want both actions to either complete successfully or fail together. By using REQUIRED
, the called service methods like saveUser()
and sendEmail()
will be part of the same transaction.
@Service public class InnerService { @Transactional(propagation = Propagation.REQUIRED) public void innerMethod() { // Will join existing transaction or start a new one // Insert into Table B } } @Service public class OuterService { @Autowired private InnerService innerService; @Transactional public void outerMethod() { // Insert into Table A innerService.innerMethod(); // Both operations part of the same transaction } }
Use case: Most business methods. Ensures atomicity across calls.
3.2 Propagation.REQUIRES_NEW
Detailed Behavior
This starts a completely new and independent transaction, even if one already exists. The current transaction is suspended until the new one completes. This new transaction will commit or roll back independently of the outer transaction.
Transaction Flow
- Always start a new transaction.
- Suspend the existing one, if any.
Real-World Use Case
In an e-commerce app, you’re saving order details and also logging analytics. Even if the order fails, you still want the analytics (like product views or clicks) to be recorded. You use REQUIRES_NEW
for the logging operation.
@Service public class InnerService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void innerMethod() { // Insert into Table B // If exception occurs here, only this transaction rolls back } } @Service public class OuterService { @Autowired private InnerService innerService; @Transactional public void outerMethod() { // Insert into Table A try { innerService.innerMethod(); } catch (Exception e) { // Handle exception } // Table A insert won't be affected by inner exception } }
Use case: Logging, auditing, or non-critical operations that shouldn’t affect the main transaction.
3.3 Propagation.NESTED
Detailed Behavior
If a transaction already exists, NESTED
will create a savepoint within it. If the nested method throws an exception, the transaction can roll back to this savepoint, allowing the outer transaction to continue. If no existing transaction is found, a new one is started (like REQUIRED
).
Transaction Flow
- Existing transaction? → Create a savepoint.
- No transaction? → Start a new transaction.
Real-World Use Case
Let’s say you are performing a bulk insert of records. You want to insert all valid records, but skip and rollback only invalid ones, without rolling back the entire batch. Use NESTED
for each insert.
@Service public class InnerService { @Transactional(propagation = Propagation.NESTED) public void innerMethod() { // Insert into Table B // Can rollback to this point without affecting outer method } } @Service public class OuterService { @Autowired private InnerService innerService; @Transactional public void outerMethod() { // Insert into Table A try { innerService.innerMethod(); } catch (Exception e) { // Only rollback inner transaction (if using DataSourceTransactionManager) } // Table A insert remains unless manually rolled back } }
Important Note: Requires a JDBC-based DataSourceTransactionManager
. Does not work with JPA’s JpaTransactionManager
.
Use case: Partial rollbacks within the same transaction.
3.4 Propagation.SUPPORTS
Detailed Behavior
If a transaction exists, this method will participate in it. If no transaction is active, it simply executes non-transactionally. This makes the method agnostic to transactional context — it supports one but doesn’t require it.
Transaction Flow
- Existing transaction? → Join it.
- No transaction? → Execute normally without one.
Real-World Use Case
You are building a read-only method (like fetching a user profile) that may or may not be called from within a transaction. You don’t want to force a transaction for performance reasons.
@Service public class InnerService { @Transactional(propagation = Propagation.SUPPORTS) public void innerMethod() { // Runs in transaction if outerMethod is transactional // Else runs without a transaction } } @Service public class OuterService { @Autowired private InnerService innerService; public void outerMethod() { innerService.innerMethod(); } }
Use case: Read-only operations that may or may not require a transaction.
3.5 Propagation.NOT_SUPPORTED
Detailed Behavior
This method is executed outside any transactional context. If a transaction is ongoing when this method is called, it is suspended for the duration of the method.
Transaction Flow
- Existing transaction? → Suspend it and run non-transactionally.
- No transaction? → Just run non-transactionally.
Real-World Use Case
In a financial system, suppose you are exporting a large set of data for reporting. You don’t want to keep transactions open during a long export operation — use NOT_SUPPORTED
.
@Service public class InnerService { @Transactional(propagation = Propagation.NOT_SUPPORTED) public void innerMethod() { // Runs outside any transaction } } @Service public class OuterService { @Autowired private InnerService innerService; @Transactional public void outerMethod() { // In transaction innerService.innerMethod(); // Runs outside transaction } }
Use case: Performance-sensitive operations like large data queries.
3.6 Propagation.NEVER
Detailed Behavior
This method must not be executed within a transaction. If a transaction exists, an exception is thrown. It enforces that the method is non-transactional.
Transaction Flow
- Existing transaction? → Throw
IllegalTransactionStateException
. - No transaction? → Run normally.
Real-World Use Case
In a cleanup or utility task (like file deletion, cache purge), you don’t want any transactional boundaries interfering — especially since these operations are not transactional by nature.
@Service public class InnerService { @Transactional(propagation = Propagation.NEVER) public void innerMethod() { // Will throw exception if a transaction is active } } @Service public class OuterService { @Autowired private InnerService innerService; @Transactional public void outerMethod() { innerService.innerMethod(); // Will throw exception } }
Use case: Prevent accidental execution within a transaction. E.g., for cleanup or maintenance logic.
3.7 Propagation.MANDATORY
Detailed Behavior
This method must be called within an existing transaction. If no transaction is active, an exception is thrown. It enforces that the method relies on transaction context for correctness.
Transaction Flow
- Existing transaction? → Join it.
- No transaction? → Throw
TransactionRequiredException
.
Real-World Use Case
You are implementing a method in a DAO layer that must be part of a transaction (e.g., update followed by save). To prevent misuse, mark it as MANDATORY
.
@Service public class InnerService { @Transactional(propagation = Propagation.MANDATORY) public void innerMethod() { // Will throw exception if not already in a transaction } } @Service public class OuterService { @Autowired private InnerService innerService; public void outerMethod() { innerService.innerMethod(); // Exception: no transaction exists } }
Use case: Enforce transactional execution in utility or DAO layers.
4. Key Points and Best Practices
- Propagation.REQUIRED is the default — suitable for most use cases.
- Use REQUIRES_NEW to isolate operations — e.g., logging, sending notifications.
- NESTED is useful when you need partial rollbacks but beware of JPA limitations.
- Avoid REQUIRES_NEW and NESTED in high-throughput paths without profiling — may cause performance overhead.
- Use MANDATORY, NEVER, and NOT_SUPPORTED cautiously for defensive coding.
- Never catch exceptions silently in transactional methods — it may prevent rollback.
- Don’t use
@Transactional
on private or self-invoked methods — Spring AOP won’t intercept them. - Always test your rollback behavior using integration tests.