Mastering Spring & MSA Transactions – Part 7: Unlocking Transaction Propagation in Spring: When to Combine, When to Separate
In our previous discussions, we discovered how @Transactional
can automatically handle commits and rollbacks in Spring. Yet, real-world scenarios raise questions like:
- “What if a method is already under a transaction, but I need a brand-new one?”
- “What if I want certain steps to be outside any transaction for performance or concurrency reasons?”
That’s exactly where transaction propagation steps in. This article explores the different propagation modes and how each fits common situations in everyday development.
1) What Is Transaction Propagation?
Propagation in Spring determines how a method annotated with @Transactional
behaves if there is already a transaction in progress:
- Should it join the existing transaction?
- Should it start a fresh transaction no matter what?
- Should it fail if a transaction exists, or if one doesn’t exist?
By configuring propagation properly, you can precisely control how your application’s operations interact with ongoing transactions.
2) Core Propagation Modes with Realistic Examples
2.1) REQUIRED
(Default)
Concept: If there’s an existing transaction, join it; otherwise, start a new one.
- Typical Use Case: Standard CRUD operations, where everything can run in a single overarching transaction.
- Example: java복사
@Transactional // implied (propagation=Propagation.REQUIRED) public void placeOrder(Order order) { // If a transaction already exists, join it // else create a brand-new transaction }
- This is the most common setting and suits most day-to-day operations.
2.2) REQUIRES_NEW
Concept: Always start a new transaction, suspending any existing one.
- When to Use: You need a fully independent transaction from the caller.
- Real-World Scenario: Let’s say you’re logging an audit entry or processing a payment record that must commit or roll back separately from the main logic. java복사
@Transactional(propagation = Propagation.REQUIRES_NEW) public void logPayment(Payment payment) { // Suspends any current transaction // Always creates a separate, brand-new transaction }
- Note: If the parent transaction fails, the logs or payment operations might still commit.
2.3) SUPPORTS
Concept: If a transaction exists, join it; if not, just run without a transaction.
- When to Use: A method can operate safely with or without a transaction. For instance, a read-only query that can participate in a larger transaction if one is active, or else proceed transaction-free. java복사
@Transactional(propagation = Propagation.SUPPORTS) public Order getOrder(Long id) { // If called within a transaction, it joins // Otherwise, no transaction context is created return orderRepository.findById(id); }
- Caution: Behavior depends on the calling context, which can lead to subtle differences in concurrency or locking.
2.4) MANDATORY
Concept: A transaction must already exist. If not, throw an exception.
- Use Case: You have logic that only makes sense within an existing transaction.
- Example: java복사
@Transactional(propagation = Propagation.MANDATORY) public void updateStock(StockChange change) { // If no active transaction, Spring throws IllegalTransactionStateException }
- Less common, but occasionally useful if you rely on a “this must be called only inside a transaction” contract.
2.5) NOT_SUPPORTED
Concept: If there’s an existing transaction, suspend it, then run without any transaction.
- Scenario: A method that performs a long-running external API call or file upload, where you don’t want to hold DB locks the entire time. java복사
@Transactional(propagation = Propagation.NOT_SUPPORTED) public void callExternalApi() { // If a parent transaction exists, suspend it // No transaction context for this method }
- This ensures you don’t tie up database resources unnecessarily.
2.6) NEVER
Concept: If there is an active transaction, throw an exception.
- When to Use: Very rarely, but occasionally you need absolute certainty no transaction is in place.
- Example: java복사
@Transactional(propagation = Propagation.NEVER) public void doNonTransactionalStuff() { // If any transaction exists, exception occurs }
2.7) NESTED
Concept: Runs in a nested savepoint inside the current transaction, allowing partial rollbacks.
- Dependent on DB support. Not frequently used but can be handy for partial rollbacks (e.g., if a part of your method fails but you want to keep prior changes).
3) Avoiding Propagation Conflicts
If the same call stack ends up with contradictory propagation rules, Spring can’t reconcile them. For instance:
@Service
@Transactional(propagation = Propagation.NEVER) // No transaction allowed
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void payInvoice(Invoice invoice) {
// This demands a brand-new transaction
// Direct conflict => IllegalTransactionStateException
}
}
- Class-level says “never create a transaction,” method-level says “always create one.” Spring throws an exception because it’s impossible to satisfy both.
4) Why So Many Options?
- Independent commits: For critical sub-tasks (e.g., separate logs or payment audits) that must not roll back if parent fails.
- Transaction-free logic**: Long external calls or large-file operations can’t be locked up in a transaction.
- Strict contexts: Some methods must only run within a transaction (
MANDATORY
), or must never have one (NEVER
).
Essentially, each propagation mode addresses specific real-world constraints on how your data operations interact.
5) Practical Advice & Patterns
- Default to
REQUIRED
for everyday create/read/update/delete tasks. It automatically merges or starts a transaction as needed. - Use
REQUIRES_NEW
sparingly for genuinely independent tasks (like logging or payment records). Overuse can cause many short-lived transactions, impacting performance. - Try
NOT_SUPPORTED
for big or time-consuming tasks where you don’t want to hold DB locks. MANDATORY
orNEVER
are specialized and can lead to errors if misapplied. Typically you’d use them in strict frameworks or specific architecture constraints.SUPPORTS
can be helpful for optional transaction usage, but keep in mind the method’s behavior changes if a transaction is present or not.
6) Putting It All Together
- Most of the time,
REQUIRED
covers standard logic well. - For sub-tasks needing independent commits or rollbacks, go
REQUIRES_NEW
. - If a method absolutely must run outside a transaction,
NOT_SUPPORTED
orNEVER
is your friend. - When you want to forcibly require a transaction or fail otherwise,
MANDATORY
is there (though seldom used). NESTED
is niche, requiring DB-level savepoint support.
Understanding these modes helps you build more robust and fine-tuned transaction flows. You can mix them as needed, but watch for outright conflicts—Spring won’t let you break the rules of logic. By carefully selecting propagation modes, you handle complex scenarios gracefully, ensuring each part of your application’s data flow behaves exactly as intended.

Enjoyed this article? Take the next step.
Future-Proof Your Java Career With Spring AI
The age of AI is here, but your Java & Spring experience isn’t obsolete—it’s your greatest asset.
This is the definitive guide for enterprise developers to stop being just coders and become the AI Orchestrators of the future.