Transaction Propagation
AdvancedREQUIRED (default, join or create), REQUIRES_NEW (always new, suspends outer), NESTED, SUPPORTS, NOT_SUPPORTED, MANDATORY, and NEVER — choose based on desired transactional boundary.
Overview
Transaction propagation controls what happens when a `@Transactional` method is called from within an existing transaction. Spring provides seven propagation types. **REQUIRED** (default): join the existing transaction or create a new one — both methods share the same commit/rollback boundary. **REQUIRES_NEW**: always start a fresh transaction, suspending the caller's — useful for audit logging that must commit even if the outer transaction rolls back. **NESTED**: use a savepoint within the outer transaction — rollback only affects the nested portion. **SUPPORTS**: join if there is a transaction, otherwise run non-transactionally. **NOT_SUPPORTED**: suspend any existing transaction and run non-transactionally. **MANDATORY**: must join an existing transaction; throws if none. **NEVER**: must not run in a transaction; throws if one exists.
REQUIRED and REQUIRES_NEW
**REQUIRED** is the right default for most service methods — they participate in whatever transaction the caller has. **REQUIRES_NEW** is the exception for operations that must succeed independently, like audit logging: if the business operation rolls back, the audit entry should still be persisted.
@Service
class OrderService {
@Transactional // REQUIRED (default)
public void placeOrder(Order order) {
orderRepo.save(order);
inventoryService.reserve(order); // joins THIS transaction
auditService.log("ORDER_PLACED"); // also joins — but we want it independent!
// If inventoryService throws, audit log is also rolled back
}
}
@Service
class AuditService {
// REQUIRES_NEW: suspend caller's transaction, open a fresh one
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String event) {
auditRepo.save(new AuditEntry(event, LocalDateTime.now()));
// This commits independently — even if placeOrder() rolls back
}
}
// Important caveat: REQUIRES_NEW does NOT work when called on the same bean
// (Spring AOP proxy is bypassed for self-invocation)
// Always inject the service as a dependency, not via this.auditService.log()NESTED, SUPPORTS, and MANDATORY
**NESTED** uses a JDBC savepoint: if the nested method throws, only changes since the savepoint are rolled back — the outer transaction can catch the exception and continue. **SUPPORTS** is useful for read methods that optionally benefit from a transaction context. **MANDATORY** is a safety assertion — call it only from within a transaction; throw otherwise.
// NESTED — partial rollback via savepoint (JPA/InnoDB only)
@Transactional(propagation = Propagation.NESTED)
public void sendNotification(Order order) {
notificationRepo.save(new Notification(order));
// If this fails, outer order save is NOT rolled back (just notification attempt)
}
@Transactional
public void placeOrder(Order order) {
orderRepo.save(order);
try {
notificationService.sendNotification(order); // NESTED
} catch (Exception e) {
log.warn("Notification failed, order still placed");
}
// order is committed even if notification rolls back
}
// MANDATORY — must be called within an existing transaction
@Transactional(propagation = Propagation.MANDATORY)
public void updateInventory(String sku, int delta) {
// Throws IllegalTransactionStateException if no active transaction
// Use as a safety check: this method MUST run in a transaction
inventoryRepo.adjustStock(sku, delta);
}The Self-Invocation Trap
Spring's `@Transactional` is implemented via AOP proxies. When a method calls **another method on the same bean**, it bypasses the proxy and the called method's `@Transactional` annotation is completely ignored. This is the most common `@Transactional` bug. Fix: inject the bean via constructor (Spring creates the proxy), use `ApplicationContext.getBean()`, or restructure the code into separate beans.
@Service
class PaymentService {
// BUG: self-invocation bypasses the proxy
public void processPayment(Payment p) {
this.charge(p); // calls directly — @Transactional on charge() IGNORED
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void charge(Payment p) {
// This NEVER runs in its own transaction when called via self-invocation
paymentRepo.save(p);
}
}
// FIX 1: inject self via constructor (Spring proxy)
@Service
class PaymentService {
private final PaymentService self; // injected proxy
public PaymentService(PaymentService self) { this.self = self; }
public void processPayment(Payment p) {
self.charge(p); // goes through proxy → @Transactional respected
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void charge(Payment p) { paymentRepo.save(p); }
}
// FIX 2 (better): extract charge() into a separate @Service beanKey Points to Remember
- 1REQUIRED (default): join existing or create new — both methods share the same transaction
- 2REQUIRES_NEW: always creates a new transaction, suspending the outer — good for audit logs
- 3NESTED: uses a JDBC savepoint — inner rollback does not affect the outer transaction
- 4MANDATORY: throws if no active transaction exists — useful as a safety assertion
- 5Self-invocation bypasses the AOP proxy — @Transactional on the called method is ignored
- 6Fix self-invocation: inject the bean's proxy via constructor or extract to a separate bean
Interview Questions
Sign in to ask AriaWhat is the difference between REQUIRED and REQUIRES_NEW propagation?
What is the self-invocation problem with @Transactional and how do you fix it?
When would you use NESTED propagation over REQUIRES_NEW?
What does MANDATORY propagation do and when is it useful?
If method A (@Transactional REQUIRED) calls method B (@Transactional REQUIRES_NEW) and B throws, does A roll back?
Ask Aria about Transaction Propagation
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.