Transactions
Master Spring transaction management — @Transactional, propagation, isolation levels, rollback rules, and the common pitfalls that cause silent data corruption in production.
Last reviewed: January 2025
🔍 What Is It?
Spring's @Transactional annotation manages database transactions declaratively.
What @Transactional does:
- Opens a transaction before the method runs
- Commits if the method returns normally
- Rolls back if a RuntimeException (or configured exception) is thrown
- Handles transaction propagation (joining existing or starting new)
Key attributes:
- →
propagation— how to handle existing transactions (REQUIRED, REQUIRES_NEW, etc.) - →
isolation— isolation level (READ_COMMITTED, REPEATABLE_READ, etc.) - →
readOnly— hint for optimization (no dirty checking, read replicas) - →
rollbackFor— exceptions that trigger rollback (default: RuntimeException) - →
noRollbackFor— exceptions that don't trigger rollback - →
timeout— transaction timeout in seconds
🎯 Why Do We Need It?
Without transactions, partial failures leave your database in inconsistent states:
// WITHOUT @Transactional — dangerous!
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
accountRepository.debit(fromId, amount); // succeeds
// *** Server crashes here ***
accountRepository.credit(toId, amount); // never runs
// Money is gone! Database is inconsistent.
}
// WITH @Transactional — safe
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
accountRepository.debit(fromId, amount);
accountRepository.credit(toId, amount);
// Either both succeed or both roll back
}🏗️ What Happens in Real Projects?
@Transactional is on every service method that writes to the database. @Transactional(readOnly=true) on every service method that only reads. Complex operations (order placement, payment processing, inventory reservation) must be in a single transaction to maintain consistency.
⚙️ How It Works
Spring creates a proxy around your @Transactional class. When you call a method, the proxy:
- Checks if there's an existing transaction (based on propagation)
- Opens a new transaction or joins existing
- Calls your method
- Commits or rolls back based on the outcome
The self-invocation trap:
When a method in a class calls another method in the SAME class, the call bypasses the proxy. The @Transactional annotation on the inner method is ignored. This is the #1 Spring transaction bug.
💻 Example
Transaction Management in Practice — Propagation, isolation, rollback rules, and common pitfalls.
🤔 Common Confusions
❓ Does @Transactional on a class apply to all methods?
Yes — @Transactional on the class sets the default for all public methods. Individual methods can override it. @Transactional(readOnly=true) on the class + @Transactional on specific write methods is a common pattern.
❓ Why doesn't @Transactional rollback on checked exceptions?
By default, Spring only rolls back on RuntimeException and Error (unchecked). For checked exceptions, use @Transactional(rollbackFor=Exception.class) or @Transactional(rollbackFor=YourCheckedException.class). This is a common gotcha — a checked exception escapes and the transaction commits partial changes.
❓ What is the difference between REQUIRED and REQUIRES_NEW?
REQUIRED (default): join the existing transaction if there is one, otherwise start a new one. REQUIRES_NEW: always start a new transaction, suspending the current one. Use REQUIRES_NEW for operations that must commit independently (audit logging, notification sending) — they should commit even if the main transaction rolls back.
🚨 Common Production Issues
Self-invocation bypassing @Transactional proxy — always call @Transactional methods from injected beans
@Transactional on private methods — Spring proxies can only intercept public methods
Checked exception not rolling back — use rollbackFor=Exception.class
Transaction too large holding database locks for too long — split into smaller transactions
LazyInitializationException outside transaction — disable open-in-view and load what you need in the transaction
✅ Best Practices
@Transactional belongs on SERVICE methods, not repository or controller methods
Use @Transactional(readOnly=true) on all read-only service methods
Keep transactions short — don't do slow operations (HTTP calls, file I/O) inside a transaction
Never call @Transactional methods from within the same class (self-invocation)
Use REQUIRES_NEW for audit logging and notification sending
Set rollbackFor=Exception.class if your methods throw checked exceptions
🏛️ Architect's Note
Long transactions are the #1 cause of database deadlocks and performance issues in production. The pattern: start transaction, do DB work, end transaction quickly. Never make HTTP calls to external services inside a transaction — if the external service takes 10 seconds, you're holding a database transaction for 10 seconds. Use the Outbox pattern: write the intent to the DB in the transaction, then process it asynchronously outside the transaction.
💬 Frequently Asked Questions
🎯 Key Takeaways
🔗 Related Topics
🎯 Practice this topic in interviews
↗ Where this topic fits
Spring Boot Technology Guide
Return to the Spring Boot learning hub.
Backend Engineer Career Path
A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.
Full Stack Java Developer Career Path
A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.
Sources
Sources / References
Use these Spring Boot references to verify the concept in official docs and continue into the most relevant SplashRide pages.
Official documentation
Related SplashRide pages
SplashRide Spring Boot learning path
Browse the full Spring Boot topic collection and learning order on SplashRide.
Spring Boot Interview Prep
Practice Spring Boot interview questions and scenario-based answers.
Backend Engineer Career Path
A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.
Full Stack Java Developer Career Path
A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.
Spring Boot Connection Pooling with HikariCP
Configure HikariCP in Spring Boot, budget connections across replicas, distinguish pool and query timeouts, and diagnose exhausted pools and connection leaks.
Spring Data JPA
Master Spring Data JPA — the abstraction layer over Hibernate that eliminates boilerplate data access code through repository interfaces and derived query methods.