Tutorials Logic, IN info@tutorialslogic.com

Spring Transactions @Transactional, Propagation, Isolation

Transaction Boundary

A Spring transaction groups database operations into one unit through a transaction manager and proxy interception. Put @Transactional on a public service operation that represents the business unit of work, not on controllers or every repository method by habit.

@Transactional Annotation

Spring's @Transactional annotation manages database transactions declaratively. When applied to a method or class, Spring wraps the execution in a transaction - committing on success and rolling back on unchecked exceptions (RuntimeException and Error) by default.

Add @EnableTransactionManagement to your configuration class (Spring Boot enables it automatically).

@Transactional with Propagation and Isolation

@Transactional with Propagation and Isolation
package com.example.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;
    private final AuditService auditService;

    public OrderService(OrderRepository orderRepository,
                        InventoryService inventoryService,
                        AuditService auditService) {
        this.orderRepository  = orderRepository;
        this.inventoryService = inventoryService;
        this.auditService     = auditService;
    }

    // Default: REQUIRED propagation, DEFAULT isolation, rollback on RuntimeException
    @Transactional
    public Order placeOrder(Long userId, Long productId, int qty) {
        // All operations share the same transaction
        inventoryService.deductStock(productId, qty); // participates in this transaction
        Order order = orderRepository.save(new Order(userId, productId, qty));
        auditService.logOrder(order); // also in same transaction
        return order;
        // Commits here if no exception
    }

    // Read-only: hint to DB to optimize (no dirty checking, no flush)
    @Transactional(readOnly = true)
    public List<Order> getOrdersByUser(Long userId) {
        return orderRepository.findByUserId(userId);
    }

    // Rollback on checked exceptions too
    @Transactional(rollbackFor = Exception.class)
    public void processPayment(Long orderId) throws Exception {
        // Rolls back even if a checked Exception is thrown
    }

    // Never rollback on IllegalArgumentException
    @Transactional(noRollbackFor = IllegalArgumentException.class)
    public void updateOrder(Long orderId, String status) {
        // IllegalArgumentException won't trigger rollback
    }

    // SERIALIZABLE isolation: highest level, prevents phantom reads
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void criticalOperation() {
        // Fully isolated from other transactions
    }

    // REQUIRES_NEW: always starts a new transaction, suspends the current one
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAuditAlways(String message) {
        // This runs in its own transaction - commits even if outer tx rolls back
        auditService.save(message);
    }
}

Propagation Types

Propagation Behavior
REQUIRED (default) Join existing transaction; create new one if none exists
REQUIRES_NEW Always create a new transaction; suspend existing one
SUPPORTS Join existing transaction; run non-transactionally if none
NOT_SUPPORTED Run non-transactionally; suspend existing transaction
MANDATORY Must run within an existing transaction; throw exception if none
NEVER Must NOT run within a transaction; throw exception if one exists
NESTED Run within a nested transaction (savepoint); rollback to savepoint on failure

Isolation Levels

Isolation Level Dirty Read Non-Repeatable Read Phantom Read
READ_UNCOMMITTED Possible Possible Possible
READ_COMMITTED Prevented Possible Possible
REPEATABLE_READ Prevented Prevented Possible
SERIALIZABLE Prevented Prevented Prevented

Programmatic Transactions with TransactionTemplate

Programmatic Transactions with TransactionTemplate
package com.example.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class ProgrammaticTxService {

    private final TransactionTemplate transactionTemplate;
    private final OrderRepository orderRepository;

    public ProgrammaticTxService(PlatformTransactionManager txManager,
                                  OrderRepository orderRepository) {
        // TransactionTemplate wraps PlatformTransactionManager
        this.transactionTemplate = new TransactionTemplate(txManager);
        this.orderRepository     = orderRepository;
    }

    public Order createOrderProgrammatically(Order order) {
        // Configure transaction settings
        transactionTemplate.setIsolationLevel(
                TransactionTemplate.ISOLATION_READ_COMMITTED);
        transactionTemplate.setTimeout(30); // seconds

        // execute() returns a value; executeWithoutResult() for void
        return transactionTemplate.execute(status -> {
            try {
                Order saved = orderRepository.save(order);
                // ... more operations
                return saved;
            } catch (Exception e) {
                // Manually mark for rollback
                status.setRollbackOnly();
                throw new RuntimeException("Order creation failed", e);
            }
        });
    }
}

Common @Transactional Pitfalls

Common @Transactional Pitfalls
@Service
public class TransactionPitfalls {

    // PITFALL 1: Self-invocation bypasses the proxy - @Transactional has no effect
    public void outerMethod() {
        this.innerMethod(); // Calls directly, NOT through Spring proxy - NO transaction!
    }

    @Transactional
    public void innerMethod() {
        // This transaction is IGNORED when called from outerMethod() above
    }

    // FIX: Inject self or use a separate bean
    @Autowired
    private TransactionPitfalls self;

    public void outerMethodFixed() {
        self.innerMethod(); // Goes through proxy - transaction works!
    }

    // PITFALL 2: @Transactional on private methods - Spring proxy can't intercept
    @Transactional
    private void privateMethod() {
        // NO transaction - Spring AOP only works on public methods
    }

    // PITFALL 3: Catching RuntimeException prevents rollback
    @Transactional
    public void catchingException() {
        try {
            // ... DB operation
            throw new RuntimeException("error");
        } catch (RuntimeException e) {
            // Swallowing the exception - transaction COMMITS instead of rolling back!
            System.out.println("Caught: " + e.getMessage());
        }
    }

    // FIX: Re-throw or mark rollback manually
    @Transactional
    public void catchAndRethrow(TransactionStatus status) {
        try {
            throw new RuntimeException("error");
        } catch (RuntimeException e) {
            // Option 1: re-throw
            throw e;
            // Option 2: mark rollback only (if you need to handle the exception)
            // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
}

Rollback and Proxies

By default, declarative transactions roll back for RuntimeException and Error; checked-exception behavior must be configured when the application contract requires rollback. Do not catch an exception inside the transaction and return success unless the transaction’s state is known and intentionally committed.

Self-invocation bypasses the proxy, so calling one @Transactional method from another method on the same object does not start a new intercepted boundary. Keep transactions short, avoid remote calls while locks are held, and select propagation and isolation only for a demonstrated consistency requirement.

Before you move on

Spring Transactions @Transactional, Propagation, Isolation Mastery Check

5 checks
  • READ_UNCOMMITTED is the possible operator; Possible produces Possible.
  • READ_COMMITTED is the prevented operator; Possible produces Possible.
  • REPEATABLE_READ is the prevented operator; Prevented produces Possible.
  • SERIALIZABLE is the prevented operator; Prevented produces Prevented.
  • Isolation Levels includes Possible, Prevented, Prevented, and Prevented.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.