Spring Transactions @Transactional, Propagation, Isolation is an important Spring topic because it appears in real projects, debugging sessions, and interviews. Learn the meaning first, then connect it to a small working example so the rule does not stay abstract.
For this page, focus on what problem Spring Transactions @Transactional, Propagation, Isolation solves, where developers usually make mistakes, and how to verify the result. The audit note for this lesson was: limited checklist/practice/mistake/FAQ notes .
A strong understanding of Spring Transactions @Transactional, Propagation, Isolation should include syntax, behavior, one realistic use case, one failure case, and one quick way to check your work with tools or output.
Spring Transactions @Transactional Propagation Isolation should be studied as a practical Spring lesson, not as a label. Start by naming the input, the rule that changes the input, and the result a learner should be able to predict after reading the page.
In the spring > spring-transactions page, the notes should connect the definition with a working scenario, a mistake that beginners actually make, and the exact check that proves the fix. That makes the topic useful for coding, debugging, and interview revision.
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).
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 | 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 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 |
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);
}
});
}
}
@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();
}
}
}
class SpringTransactionsTransactionalPropagationIsolationReview {
public static void main(String[] args) {
String state = "ready";
System.out.println("Spring Transactions @Transactional Propagation Isolation: " + state);
}
}
String value = null;
if (value == null) {
System.out.println("Spring Transactions @Transactional Propagation Isolation: handle the missing value before continuing");
}
Memorizing Spring Transactions @Transactional Propagation Isolation without the situation where it is useful.
Connect Spring Transactions @Transactional Propagation Isolation to a concrete Spring task.
Testing Spring Transactions @Transactional Propagation Isolation only with the perfect input.
Include empty, missing, duplicate, incompatible, or failed cases when relevant.
Changing code before reading the visible symptom or error message.
Inspect the output, state, configuration, or stack trace connected to Spring Transactions @Transactional Propagation Isolation.
Memorizing Spring Transactions @Transactional Propagation Isolation without the situation where it is useful.
Connect Spring Transactions @Transactional Propagation Isolation to a concrete Spring task.
The common mistake is memorizing syntax without understanding when the behavior changes or fails.
Remember the problem it solves in Spring, then attach the syntax or steps to that problem.
You can predict the result of a small example, explain a failure case, and choose it over a nearby alternative for a clear reason.
They often copy the syntax but skip the state, input, dependency, selector, route, type, or configuration that controls the behavior.
Explore 500+ free tutorials across 20+ languages and frameworks.