Sitemap
Javarevisited

A humble place to learn Java and Programming better.

Spring Transaction Propagation Guide

--

Spring Transaction Propagation: Complete Guide

Transaction Propagation in Spring

When service methods call other service methods, Spring manages how transactions “flow” between them. This is controlled by the propagation attribute in the @Transactional annotation.

Basic Scenarios

Default Behavior (REQUIRED)

  • When ServiceA.methodA() calls ServiceB.methodB(), both execute in the same transaction
  • If any method throws an exception, everything rolls back
  • This is what you’re using in your code, which is appropriate for most cases

Independent Transactions (REQUIRES_NEW)

  • Creates a completely new transaction, suspending the current one
  • If the inner method succeeds, its changes are committed even if the outer method fails
  • Perfect for operations like logging or auditing that should persist regardless of the main transaction

Nested Transactions (NESTED)

  • Creates a “savepoint” within the current transaction
  • If the inner method fails, only its changes roll back
  • If the outer method fails, everything rolls back
  • Useful for batch processing where you want to process as many items as possible

Core Concepts

Transactions in Spring manage a sequence of database operations to be treated as a single unit. When service methods call other service methods, Spring needs to know how to handle the transaction behavior.

Key Transaction Attributes

  • Propagation: Defines how transactions relate to each other when methods are nested
  • Isolation: Defines the data visibility between concurrent transactions
  • Timeout: Maximum time a transaction may take
  • Read-Only: Hint to the database for optimization
  • Rollback Rules: Define which exceptions cause rollback

Propagation Options

REQUIRED (Default)

  • Behavior: Uses existing transaction if available; creates a new one if none exists
  • Use Case: Most common scenario for business logic
  • Example:
@Transactional(propagation = Propagation.REQUIRED)public void methodA() {    // Uses its own transaction if called directly    // OR uses caller's transaction if called from a transactional method    serviceB.methodB(); // Joins methodA's transaction}

REQUIRES_NEW

  • Behavior: Always creates a new transaction, suspending the current one if it exists
  • Use Case: Operations that should commit/rollback independently
  • Example:
@Transactional(propagation = Propagation.REQUIRES_NEW)public void methodB() {    // Always runs in a new transaction    // If methodA fails, methodB can still commit if already executed}

NESTED

  • Behavior: Creates a savepoint within the current transaction
  • Use Case: When you want the option to rollback part of a transaction
  • Example:
@Transactional(propagation = Propagation.NESTED)public void methodC() {    // Creates a savepoint in the existing transaction    // If methodC fails, only its changes rollback    // If outer transaction fails, everything rolls back}

SUPPORTS

  • Behavior: Uses existing transaction if available, otherwise non-transactional
  • Use Case: Methods that can work with or without a transaction
  • Example:
@Transactional(propagation = Propagation.SUPPORTS)public void methodD() {    // Runs in a transaction only if caller has one}

NOT_SUPPORTED

  • Behavior: Executes non-transactionally, suspending current transaction if any
  • Use Case: Operations that should not be part of a transaction (e.g., read-only operations)
  • Example:
@Transactional(propagation = Propagation.NOT_SUPPORTED)public void methodE() {    // Always runs non-transactionally    // Existing transactions are suspended and resumed after}

NEVER

  • Behavior: Executes non-transactionally, throws exception if transaction exists
  • Use Case: Enforcing that a method must not be called within a transaction
  • Example:
@Transactional(propagation = Propagation.NEVER)public void methodF() {    // Throws exception if called from a transactional context}

MANDATORY

  • Behavior: Must run within existing transaction, throws exception if none exists
  • Use Case: Ensuring a method is only called within a transaction context
  • Example:
@Transactional(propagation = Propagation.MANDATORY)public void methodG() {    // Throws exception if no active transaction}

Common Use Cases

Case 1: All-or-Nothing Process

Use REQUIRED for all methods to ensure all operations commit or roll back together.

Case 2: Independent Logging or Auditing

Use REQUIRES_NEW for logging/auditing operations that should succeed even if the main transaction fails.

@Service
public class OrderService {
@Autowired private AuditService auditService;

@Transactional
public void processOrder(Order order) {
// Process order (may fail)
// Audit should succeed even if order processing fails
auditService.logOrderAttempt(order); // Uses REQUIRES_NEW
}
}

Case 3: Partial Rollback

Use NESTED when you want the option to roll back part of a transaction without affecting the rest.

@Service
public class BatchProcessingService {
@Transactional
public void processBatch(List<Item> items) {
for (Item item : items) {
try {
// If one item fails, only its changes roll back
itemProcessor.process(item); // Uses NESTED
} catch (Exception e) {
// Handle error, but continue with other items
}
}
// Batch metadata still saved even if some items failed
}
}

Transaction Isolation Levels

Complement propagation with appropriate isolation levels:

  • DEFAULT: Database default (usually READ_COMMITTED)
  • READ_UNCOMMITTED: Lowest isolation, allows dirty reads (seeing uncommitted changes)
  • READ_COMMITTED: Prevents dirty reads, but allows non-repeatable reads
  • REPEATABLE_READ: Prevents non-repeatable reads, but allows phantom reads
  • SERIALIZABLE: Highest isolation, prevents all concurrency anomalies

Example:

@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED
)
public void methodH() {
// Uses READ_COMMITTED isolation level
}

Best Practices

  1. Keep transactions short: Long-running transactions lead to database contention
  2. Place @Transactional on service methods: Not on controllers or repositories
  3. Be aware of self-invocation: Calling a @Transactional method from within the same class bypasses the proxy
  4. Consider read-only for queries: Use @Transactional(readOnly = true) for optimization
  5. Understand exception handling: Only unchecked exceptions trigger rollback by default
  6. Use specific rollback rules: @Transactional(rollbackFor = {Exception.class}) to roll back on checked exceptions

Debugging Transaction Issues

Enable transaction logging:

logging.level.org.springframework.transaction=TRACE
logging.level.org.springframework.orm.jpa=DEBUG

Common Pitfalls to Avoid

  1. Self-invocation: Calling a @Transactional method from within the same class bypasses the proxy, so the transaction won't work as expected
  2. Checked Exceptions: By default, Spring only rolls back on unchecked exceptions. Use rollbackFor = Exception.class to include checked exceptions (which you're already doing)
  3. Transaction Visibility: Changes made within a transaction aren’t visible to other transactions until committed

The artifacts I’ve created provide a visual representation of how transactions flow in different scenarios, along with a comprehensive guide to Spring’s transaction management options. The diagram shows the three main propagation types, while the guide covers all options with code examples and use cases.

Javarevisited
Javarevisited

Published in Javarevisited

A humble place to learn Java and Programming better.

Waqar
Waqar

Written by Waqar

Software Developer, I am interested in exploring my curiosity and sharing what I learn along the way.

No responses yet