DEV Community

Dev Cookies
Dev Cookies

Posted on

🌿 Understanding Transaction Propagation in Spring Boot

When building enterprise-level applications with Spring Boot, transaction management is critical to ensure data integrity. Spring provides robust support for declarative transactions using the @Transactional annotation. One of the most powerful aspects of this annotation is the propagation behavior.

In this post, we'll cover:

  • What is transaction propagation?
  • Different propagation types in Spring
  • Real-world use cases for each type
  • Code examples

πŸ” What is Transaction Propagation?

Transaction propagation determines how Spring should handle transaction boundaries when a method annotated with @Transactional is called within the context of another transaction.

In simpler terms:

"If method A is already running in a transaction, and it calls method B (which is also transactional), what should happen to method B?"


🧭 Propagation Types

Spring defines the following propagation behaviors in the Propagation enum:

Propagation Type Description
REQUIRED Join current transaction or create a new one if none exists
REQUIRES_NEW Always start a new transaction, suspending the existing one
NESTED Execute within a nested transaction if a current one exists
SUPPORTS Join the current transaction if available; else run non-transactionally
NOT_SUPPORTED Always run non-transactionally, suspending any existing transaction
NEVER Must run non-transactionally; throws exception if transaction exists
MANDATORY Must join an existing transaction; throws exception if none exists

βœ… Common Use Cases with Examples

1. REQUIRED (default)

@Transactional(propagation = Propagation.REQUIRED)
public void processOrder() {
    saveOrder();
    updateInventory();
}
Enter fullscreen mode Exit fullscreen mode

🧠 Use case: Most common. Use when all method calls should participate in the same transaction β€” either all succeed or all fail.


2. REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAuditTrail() {
    // Save audit log even if outer transaction fails
}
Enter fullscreen mode Exit fullscreen mode

🧠 Use case:
Useful when you want to persist certain changes regardless of the outcome of the parent transaction.

πŸ”„ Real-world example: Logging audit events, sending emails, or payment retries that should not roll back with business failure.


3. NESTED

@Transactional(propagation = Propagation.NESTED)
public void applyDiscount() {
    // Rollback this logic only, not the parent transaction
}
Enter fullscreen mode Exit fullscreen mode

🧠 Use case:
Allows partial rollbacks within the main transaction using savepoints.

⚠️ Note: Your database must support savepoints (e.g., H2, PostgreSQL, Oracle). MySQL with MyISAM doesn't.


4. SUPPORTS

@Transactional(propagation = Propagation.SUPPORTS)
public List<Product> getAllProducts() {
    return productRepository.findAll();
}
Enter fullscreen mode Exit fullscreen mode

🧠 Use case:
Use when the method can work with or without a transaction (read-only operations).


5. NOT_SUPPORTED

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendEmailNotification() {
    // Should not participate in any transaction
}
Enter fullscreen mode Exit fullscreen mode

🧠 Use case:
Non-transactional operations (e.g., external API calls) that shouldn’t be part of the transaction to avoid rollback delays.


6. NEVER

@Transactional(propagation = Propagation.NEVER)
public void loadStaticData() {
    // Throws exception if a transaction exists
}
Enter fullscreen mode Exit fullscreen mode

🧠 Use case:
For methods that must not be called within a transaction, such as logging frameworks or async tasks.


7. MANDATORY

@Transactional(propagation = Propagation.MANDATORY)
public void updateUserPoints() {
    // Only works if called from within a transaction
}
Enter fullscreen mode Exit fullscreen mode

🧠 Use case:
To enforce that certain methods must be part of an active transaction β€” useful for modular business services.


⚠️ Common Pitfalls

  1. Calling transactional methods within the same class: Spring proxies won't apply the transaction.
  • πŸ’‘ Fix: Move the transactional method to a different class or use AopContext.
  1. Confusing REQUIRES_NEW and NESTED: They behave differently under rollback scenarios.

  2. REQUIRES_NEW misuse: Overuse can cause performance issues due to excessive transaction overhead.


🏁 Conclusion

Spring's transaction propagation gives you fine-grained control over how transactions behave across service layers. Choosing the right propagation type depends on the business requirements, rollback strategies, and data integrity constraints.


πŸ“Œ Quick Reference

Propagation Joins Existing Txn? Creates New Txn? Throws if None Exists
REQUIRED βœ… βœ… (if none) ❌
REQUIRES_NEW ❌ (suspends) βœ… ❌
NESTED βœ… (nested savepoint) βœ… (if none) ❌
SUPPORTS βœ… ❌ ❌
NOT_SUPPORTED ❌ (suspends) ❌ ❌
NEVER ❌ ❌ βœ…
MANDATORY βœ… ❌ βœ…

Top comments (1)

Collapse
 
karan_patel_d1fea60c1e533 profile image
Karan Patel

I recently made a Commercial grade Project using Spring Boot. Would love your feedback or suggestions on how I can expand and improve it, check out here
linkedin.com/feed/update/urn:li:ac...