Full Stack • Java • System Design • Cloud • AI Engineering

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.