Transaction Propagation in Spring
Complete guide to Spring Transaction Propagation with REQUIRED, REQUIRES_NEW, NESTED, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER, real examples, rollback behavior, diagrams, and interview questions.
What is Transaction Propagation?
Transaction Propagation defines how a transactional method behaves when it is called from another transactional method.
Simple meaning:
When Method A calls Method B,
should Method B use the same transaction,
create a new transaction,
or run without transaction?
Spring controls this using:
@Transactional(propagation = Propagation.REQUIRED)
Why Transaction Propagation is Important
In real applications, one business flow may call multiple services.
Example:
Place Order
↓
Save Order
↓
Reduce Inventory
↓
Make Payment
↓
Send Notification
↓
Write Audit Log
Should all steps rollback together?
Or should audit log save even if payment fails?
Transaction propagation answers this.
Real World Example
Imagine online shopping.
Order placed
Payment failed
Inventory reduced
Audit log created
Notification sent
You need rules:
Order + Inventory + Payment
rollback together
Audit Log
save independently
Notification
no database transaction needed
Main Propagation Types
| Propagation | Meaning |
|---|---|
| REQUIRED | Join existing transaction or create new one |
| REQUIRES_NEW | Always create a new transaction |
| NESTED | Create nested transaction using savepoint |
| SUPPORTS | Use transaction if exists, otherwise no transaction |
| NOT_SUPPORTED | Always run without transaction |
| MANDATORY | Transaction must already exist |
| NEVER | Transaction must not exist |
Default Propagation
Spring default is:
@Transactional
Same as:
@Transactional(propagation = Propagation.REQUIRED)
Sample Domain
We will use order processing example.
@Entity
public class Order {
@Id
private Long id;
private String itemName;
private String status;
}
@Entity
public class Payment {
@Id
private Long id;
private Long orderId;
private String status;
}
@Entity
public class AuditLog {
@Id
private Long id;
private String message;
}
1. REQUIRED Propagation
Meaning
Use existing transaction if available.
If no transaction exists, create new transaction.
This is the default behavior.
REQUIRED Diagram
flowchart TD
A["OrderService Transaction"]
B["saveOrder REQUIRED"]
C["paymentService REQUIRED"]
D["inventoryService REQUIRED"]
E["Single Shared Transaction"]
A --> B
A --> C
A --> D
B --> E
C --> E
D --> E
REQUIRED Example
@Service
public class OrderService {
private final PaymentService paymentService;
private final OrderRepository orderRepository;
public OrderService(
PaymentService paymentService,
OrderRepository orderRepository) {
this.paymentService = paymentService;
this.orderRepository = orderRepository;
}
@Transactional
public void placeOrder() {
Order order = new Order();
order.setId(1L);
order.setItemName("Laptop");
order.setStatus("CREATED");
orderRepository.save(order);
paymentService.processPayment(1L);
order.setStatus("CONFIRMED");
}
}
@Service
public class PaymentService {
private final PaymentRepository paymentRepository;
public PaymentService(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}
@Transactional(propagation = Propagation.REQUIRED)
public void processPayment(Long orderId) {
Payment payment = new Payment();
payment.setId(101L);
payment.setOrderId(orderId);
payment.setStatus("SUCCESS");
paymentRepository.save(payment);
}
}
REQUIRED Success Flow
placeOrder starts transaction
save order
process payment joins same transaction
update order status
commit everything
REQUIRED Rollback Example
@Transactional
public void placeOrder() {
orderRepository.save(order);
paymentService.processPayment(order.getId());
throw new RuntimeException("Something failed");
}
Result:
Order rollback
Payment rollback
Because both use the same transaction.
REQUIRED Rollback Diagram
flowchart TD
A["Start Transaction T1"]
B["Save Order"]
C["Save Payment"]
D["Runtime Exception"]
E["Rollback T1"]
F["Order Not Saved"]
G["Payment Not Saved"]
A --> B
B --> C
C --> D
D --> E
E --> F
E --> G
2. REQUIRES_NEW Propagation
Meaning
Always suspend current transaction.
Start a completely new transaction.
Useful for audit logs, notifications, retry records, failure records.
REQUIRES_NEW Diagram
flowchart TD
A["Start Order Transaction T1"]
B["Save Order in T1"]
C["Suspend T1"]
D["Start Audit Transaction T2"]
E["Save Audit Log in T2"]
F["Commit T2"]
G["Resume T1"]
H["Payment Fails"]
I["Rollback T1"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
REQUIRES_NEW Example
@Service
public class AuditService {
private final AuditLogRepository auditLogRepository;
public AuditService(AuditLogRepository auditLogRepository) {
this.auditLogRepository = auditLogRepository;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditLog(String message) {
AuditLog log = new AuditLog();
log.setId(System.currentTimeMillis());
log.setMessage(message);
auditLogRepository.save(log);
}
}
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final AuditService auditService;
public OrderService(
OrderRepository orderRepository,
AuditService auditService) {
this.orderRepository = orderRepository;
this.auditService = auditService;
}
@Transactional
public void placeOrder() {
Order order = new Order();
order.setId(1L);
order.setItemName("Laptop");
order.setStatus("CREATED");
orderRepository.save(order);
auditService.saveAuditLog("Order created");
throw new RuntimeException("Payment failed");
}
}
Result:
Order rollback
Audit log committed
Why Audit Log Is Saved?
Because audit log uses:
Propagation.REQUIRES_NEW
It commits in its own transaction before the outer transaction fails.
REQUIRES_NEW Use Cases
✅ Audit logging
✅ Failure tracking
✅ Email status records
✅ Outbox event save
✅ Independent business step
3. NESTED Propagation
Meaning
Use current transaction,
but create a savepoint inside it.
If nested method fails, Spring can rollback only to savepoint.
Outer transaction can continue.
NESTED Diagram
flowchart TD
A["Start Transaction T1"]
B["Save Order"]
C["Create Savepoint"]
D["Try Coupon Apply"]
E["Coupon Failed"]
F["Rollback To Savepoint"]
G["Continue Order Flow"]
H["Commit T1"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
NESTED Example
@Service
public class CouponService {
@Transactional(propagation = Propagation.NESTED)
public void applyCoupon(Long orderId) {
// save coupon usage
// if coupon is invalid, rollback only this nested part
throw new RuntimeException("Invalid coupon");
}
}
@Service
public class OrderService {
private final CouponService couponService;
private final OrderRepository orderRepository;
@Transactional
public void placeOrder() {
orderRepository.save(order);
try {
couponService.applyCoupon(order.getId());
} catch (Exception ex) {
System.out.println("Coupon failed. Continue without coupon.");
}
order.setStatus("CONFIRMED");
}
}
Result:
Order committed
Coupon changes rollback
NESTED vs REQUIRES_NEW
| Feature | NESTED | REQUIRES_NEW |
|---|---|---|
| Transaction | Same outer transaction | Separate transaction |
| Savepoint | Yes | No |
| Outer suspended | No | Yes |
| Commit independent | No | Yes |
| Rollback inner only | Yes | Yes |
| DB support needed | Savepoint support | Separate connection |
4. SUPPORTS Propagation
Meaning
If transaction exists, join it.
If no transaction exists, run without transaction.
Useful for read-only helper methods.
SUPPORTS Diagram
flowchart TD
A["Caller Has Transaction"]
B["SUPPORTS Method"]
C["Join Existing Transaction"]
D["Caller Has No Transaction"]
E["Run Without Transaction"]
A --> B
B --> C
D --> B
B --> E
SUPPORTS Example
@Service
public class ReportService {
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Order getOrder(Long orderId) {
return orderRepository.findById(orderId)
.orElseThrow();
}
}
Behavior:
Called from transactional method
joins transaction
Called from non-transactional method
runs without transaction
5. NOT_SUPPORTED Propagation
Meaning
Suspend existing transaction.
Run without transaction.
Useful for slow external calls or read-only operations where transaction is not needed.
NOT_SUPPORTED Diagram
flowchart TD
A["Transaction T1 Running"]
B["Call NOT_SUPPORTED Method"]
C["Suspend T1"]
D["Run Without Transaction"]
E["Finish Method"]
F["Resume T1"]
A --> B
B --> C
C --> D
D --> E
E --> F
NOT_SUPPORTED Example
@Service
public class NotificationService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendEmail(Long orderId) {
// Call external email API
// No database transaction needed
System.out.println("Email sent for order " + orderId);
}
}
Why?
Do not keep DB transaction open while calling external API.
6. MANDATORY Propagation
Meaning
Must have existing transaction.
If no transaction exists, throw exception.
Useful when a method must only run inside a larger business transaction.
MANDATORY Diagram
flowchart TD
A["Caller With Transaction"]
B["MANDATORY Method"]
C["Join Transaction"]
D["Caller Without Transaction"]
E["Throw Exception"]
A --> B
B --> C
D --> B
B --> E
MANDATORY Example
@Service
public class InventoryService {
@Transactional(propagation = Propagation.MANDATORY)
public void reduceStock(Long productId) {
// Must be part of order transaction
System.out.println("Stock reduced");
}
}
If called without transaction:
IllegalTransactionStateException
7. NEVER Propagation
Meaning
Must run without transaction.
If transaction exists, throw exception.
Useful for methods that should never participate in a transaction.
NEVER Diagram
flowchart TD
A["Caller Without Transaction"]
B["NEVER Method"]
C["Run Normally"]
D["Caller With Transaction"]
E["Throw Exception"]
A --> B
B --> C
D --> B
B --> E
NEVER Example
@Service
public class HealthCheckService {
@Transactional(propagation = Propagation.NEVER)
public String health() {
return "UP";
}
}
Propagation Quick Decision Guide
| Requirement | Use |
|---|---|
| Default business method | REQUIRED |
| Audit log should save even if main fails | REQUIRES_NEW |
| Rollback inner part only using savepoint | NESTED |
| Read method can work with or without transaction | SUPPORTS |
| External API should run outside DB transaction | NOT_SUPPORTED |
| Must be called inside transaction | MANDATORY |
| Must never run inside transaction | NEVER |
Important Rollback Rule
By default, Spring rolls back only for unchecked exceptions:
RuntimeException
Error
It does not rollback for checked exceptions unless configured.
Runtime Exception Example
@Transactional
public void placeOrder() {
orderRepository.save(order);
throw new RuntimeException("Failed");
}
Result:
Rollback happens
Checked Exception Example
@Transactional
public void placeOrder() throws Exception {
orderRepository.save(order);
throw new Exception("Checked exception");
}
Result by default:
No rollback
Force Rollback for Checked Exception
@Transactional(rollbackFor = Exception.class)
public void placeOrder() throws Exception {
orderRepository.save(order);
throw new Exception("Checked exception");
}
Result:
Rollback happens
Self Invocation Problem
Very important interview topic.
Spring transactions work through proxy.
This will not apply propagation correctly:
@Service
public class OrderService {
@Transactional
public void placeOrder() {
saveAuditLog();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditLog() {
// This REQUIRES_NEW may not work
}
}
Why?
Method call is inside same class.
Spring proxy is bypassed.
Correct:
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditLog() {
// Works because called through Spring proxy
}
}
Self Invocation Diagram
flowchart TD
A["External Caller"]
B["Spring Proxy"]
C["OrderService placeOrder"]
D["Internal call saveAuditLog"]
E["Proxy Bypassed"]
F["REQUIRES_NEW Not Applied"]
A --> B
B --> C
C --> D
D --> E
E --> F
Complete Order Flow Example
@Service
public class OrderFacade {
private final OrderService orderService;
private final AuditService auditService;
private final NotificationService notificationService;
@Transactional
public void placeOrder() {
try {
orderService.createOrder();
auditService.saveAuditLog("Order created");
notificationService.sendEmail();
orderService.confirmOrder();
} catch (Exception ex) {
auditService.saveAuditLog("Order failed");
throw ex;
}
}
}
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditLog(String message) {
// independent transaction
}
}
@Service
public class NotificationService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendEmail() {
// no transaction
}
}
Complete Flow Diagram
flowchart TD
A["OrderFacade placeOrder"]
B["Start Main Transaction T1"]
C["Create Order REQUIRED"]
D["Audit Log REQUIRES_NEW"]
E["Suspend T1"]
F["Start T2"]
G["Commit T2"]
H["Resume T1"]
I["Send Email NOT_SUPPORTED"]
J["Suspend T1 Again"]
K["Run Without Transaction"]
L["Resume T1 Again"]
M["Confirm Order REQUIRED"]
N["Commit T1"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
G --> H
H --> I
I --> J
J --> K
K --> L
L --> M
M --> N
Common Mistakes
❌ Expecting REQUIRES_NEW to work inside same class method call
❌ Using transaction around slow external API calls
❌ Assuming checked exceptions rollback automatically
❌ Using REQUIRES_NEW everywhere
❌ Forgetting outer transaction can still rollback
❌ Confusing NESTED and REQUIRES_NEW
Best Practices
✅ Use REQUIRED for normal business flows
✅ Use REQUIRES_NEW for audit or independent logging
✅ Use NOT_SUPPORTED for external API calls
✅ Keep transactions short
✅ Avoid self-invocation for transactional methods
✅ Use rollbackFor for checked exceptions when needed
✅ Keep transactional methods public
Interview Questions
Q1. What is transaction propagation?
It defines how a transactional method behaves when called from another transactional method.
Q2. What is default propagation?
Propagation.REQUIRED
Q3. Difference between REQUIRED and REQUIRES_NEW?
REQUIRED joins existing transaction.
REQUIRES_NEW suspends existing transaction and starts new one.
Q4. When should we use REQUIRES_NEW?
For audit logs, failure records, outbox events, and independent DB operations.
Q5. Difference between NESTED and REQUIRES_NEW?
NESTED uses savepoint inside same transaction.
REQUIRES_NEW creates completely separate transaction.
Q6. Why does transaction not work in same class method call?
Because Spring uses proxy-based AOP. Internal method call bypasses proxy.
Q7. Which exceptions trigger rollback by default?
RuntimeException
Error
Checked exceptions do not rollback by default.
Summary
Transaction propagation decides transaction behavior between service methods.
Remember:
REQUIRED
Join or create transaction
REQUIRES_NEW
Always create independent transaction
NESTED
Use savepoint inside current transaction
SUPPORTS
Join if exists, otherwise no transaction
NOT_SUPPORTED
Always run without transaction
MANDATORY
Must have existing transaction
NEVER
Must not have transaction
Best rule:
Use REQUIRED by default.
Use REQUIRES_NEW only when data must commit independently.
Use NOT_SUPPORTED for external calls.
Use NESTED when savepoint behavior is required.