I Found @Transactional on 400+ Methods in Production. Half of Them Were Unnecessary.
Your @Transactional Method Might Not Be Working (And You Probably Don't Know Why)
I Found @Transactional on 400+ Methods in Production. Half of Them Were Unnecessary.

Hitesh Laxman Transactional Method Spring Boot Application Development
Your @Transactional Method Might Not Be Working (And You Probably Don't Know Why)
Friendly Link
- I Found
@Transactionalon 400+ Methods in Production. Half of Them Were Unnecessary. - The Hidden Cost of
@TransactionalThat Most Spring Boot Developers Ignore - Why Your
@TransactionalIsn't Working (Even Though There Are No Errors) - Stop Putting
@TransactionalEverywhere. Here's What Really Happens. - Everything You Need to Know About Spring Boot
@Transactional(With Real Production Examples)
Spring Boot Transactional, @Transactional Interview Questions, Spring Transaction Management, Transaction Propagation, Spring Boot Performance, Transaction Isolation, Spring Proxy, Java Backend Interview, Spring Boot Best Practices, Distributed Transactions, Microservices Transactions
It was 2:17 AM.
Production was throwing inconsistent data.
One table was updated.
Another wasn’t.
There was no exception.
No rollback.
Everything looked correct.
Even worse…
The service method had **@Transactional**.
The team spent nearly 8 hours debugging before discovering something shocking.
The transaction never started.
Because Spring never intercepted the method call.
That day we realized something every Spring Boot developer should know:
Adding
@Transactionaldoes NOT guarantee a transaction.
Let’s see what actually happens behind the scenes.
What is @Transactional?
Most developers think
@Transactional
public void transferMoney() {
...
}
means
“Everything inside this method is atomic.”
Not exactly.
It means
“If Spring can intercept this method through its proxy, it will create a transaction.”
That difference changes everything.
What Actually Happens Internally?
Controller
│
▼
Spring Proxy
│
▼
TransactionInterceptor
│
▼
PlatformTransactionManager
│
▼
Database Connection
│
▼
BEGIN TRANSACTION
│
▼
Execute Business Logic
│
▼
COMMIT
or
ROLLBACK
Without the proxy…
No transaction exists.
Step-by-Step Internal Working
Step 1
Spring scans
@Transactional
during startup.
Step 2
Spring creates a proxy around your bean.
UserService
↓
Proxy(UserService)
Every method call first goes through the proxy.
Step 3
Proxy asks
Is this method transactional?
If yes
Open Connection
Disable Auto Commit
BEGIN TRANSACTION
Step 4
Business logic executes.
saveUser();
saveAddress();
saveOrders();
Step 5
If no exception
COMMIT
If RuntimeException
ROLLBACK
What Does Spring Actually Create?
Usually
JDK Dynamic Proxy
or
CGLIB Proxy
depending on whether interfaces exist.
That’s why proxy limitations exist.
Hidden Cost Nobody Talks About
Many developers do this
@Service
public class UserService {
@Transactional
public User findUser(Long id) {
return repository.findById(id);
}
@Transactional
public List<User> findAll() {
return repository.findAll();
}
}
Every request now
- Opens transaction context
- Gets connection
- Binds connection to thread
- Creates synchronization objects
- Performs transaction checks
- Commits transaction
Even for
SELECT
This is unnecessary overhead.
Read-Only Transactions
Instead
@Transactional(readOnly = true)
public List<User> getUsers() {
return repository.findAll();
}
Benefits
✓ Better Hibernate optimization
✓ Dirty checking disabled
✓ Less memory usage
✓ Better performance
Cost of Every Transaction
Every transaction involves
✅ Getting database connection
✅ Disabling auto-commit
✅ Creating transaction context
✅ ThreadLocal storage
✅ Synchronization registration
✅ Flush checking
✅ Commit/Rollback
✅ Connection release
Thousands of unnecessary transactions every second become expensive.
Biggest Mistakes Developers Make
1. Calling Method Inside Same Class
@Service
public class UserService {
public void create() {
save();
}
@Transactional
public void save() {
}
}
Transaction?
❌ NO
Because
this.save()
doesn’t go through Spring proxy.
Correct
Move transactional method to another bean.
@Service
public class SaveService {
@Transactional
public void save(){}
}
@Service
public class UserService{
@Autowired
SaveService service;
public void create(){
service.save();
}
}
2. Private Methods
@Transactional
private void save(){}
Won’t work.
Spring cannot proxy private methods.
3. Final Methods
@Transactional
public final void save(){}
Also ignored by CGLIB proxies.
4. Checked Exceptions
throw new IOException();
Spring does NOT rollback by default.
Need
@Transactional(rollbackFor = IOException.class)
5. Async Methods
@Async
@Transactional
Different thread.
Different transaction.
Developers often misunderstand this behavior.
Propagation Explained
Default
REQUIRED
Join existing transaction.
Otherwise create new.
Always new
REQUIRES_NEW
Useful for
Audit Logs
Notification Logs
Payment Logs
Never transactional
NOT_SUPPORTED
Suspend transaction.
Useful for reporting.
Mandatory
MANDATORY
Must already have transaction.
Otherwise exception.
Isolation Levels
Read Uncommitted
Dirty reads possible.
Read Committed
Default in PostgreSQL.
Repeatable Read
Prevents non-repeatable reads.
Serializable
Highest consistency.
Lowest performance.
When Should You Use @Transactional?
✅ Money transfer
✅ Inventory update
✅ Order placement
✅ Payment processing
✅ Booking systems
✅ Account balance updates
✅ Multiple table updates
✅ Business operations that must succeed together
When NOT to Use It
❌ Simple SELECT queries (unless readOnly = true)
❌ Utility methods
❌ Validation logic
❌ Data mapping
❌ External API calls
❌ File uploads
❌ Long-running loops
❌ Kafka publishing (unless carefully coordinated)
❌ Sending emails
❌ Report generation
Reason: Long-running transactions keep database connections and locks open, reducing throughput and increasing contention.
External API Inside Transaction? Think Twice
Avoid this:
@Transactional
public void placeOrder() {
orderRepository.save(order);
paymentGateway.charge();
shippingApi.createShipment();
}
If the external API takes 10 seconds, the database transaction stays open for 10 seconds.
Better:
@Transactional
public Long createOrder(Order order) {
return orderRepository.save(order).getId();
}
// After commit
paymentGateway.charge(orderId);
shippingApi.createShipment(orderId);
Or publish an event after commit using @TransactionalEventListener.
Does @Transactional Work Across Microservices?
Service A
↓
Service B
Both use
@Transactional
One transaction?
❌ No.
Each service has its own transaction manager and database connection.
Use Saga Pattern, Outbox Pattern, or distributed transaction solutions if true cross-service consistency is required.
Multiple Databases?
Interview Question #2
@Transactional
DB1
DB2
One transaction?
Not automatically.
Each datasource has its own transaction manager. Coordinating them requires XA/JTA or an application-level pattern such as Saga. XA adds complexity and is uncommon in modern microservices.
Does @Transactional Work on Private Methods?
Interview Question #3
Answer
❌ No.
Proxy cannot intercept private methods.
Why Doesn’t Self Invocation Work?
Interview Question #4
this.save();
Bypasses Spring proxy.
No transaction.
Which Exceptions Trigger Rollback?
Interview Question #5
Default
RuntimeException
Error
Not
Checked Exceptions
unless configured.
Bonus Interview Questions
Can @Transactional work with @Async?
No.
Different thread.
Different transaction context.
Can transaction span Kafka + Database?
Not by default.
Use Transactional Outbox Pattern or Kafka transactions where appropriate. Do not assume a database transaction automatically includes Kafka.
Can @Transactional be used on Controller?
Technically yes.
Recommended?
No.
Transactions belong in the service layer.
Can one transaction call another?
Yes.
Depends on propagation.
REQUIRED
REQUIRES_NEW
NESTED
What happens if transaction timeout occurs?
Spring marks the transaction for rollback, and the underlying transaction manager aborts it.
Production Best Practices
✅ Keep transactions short.
✅ Only wrap business logic that requires atomicity.
✅ Use readOnly = true for read operations.
✅ Never keep transactions open while waiting for external systems.
✅ Don’t call transactional methods from the same class.
✅ Handle checked exceptions intentionally.
✅ Understand propagation before using REQUIRES_NEW.
✅ Monitor slow transactions in production.
Final Thoughts
@Transactional is one of Spring's most powerful features—but it's also one of the most misunderstood.
A single annotation hides a sophisticated mechanism involving proxies, transaction managers, connection handling, rollback rules, and propagation semantics. Used correctly, it protects your data. Used carelessly, it can introduce subtle bugs, unnecessary overhead, and difficult production issues.
The next time you type:
@Transactional
ask yourself:
“Do I really need a transaction here, and will Spring actually create one?”
That simple question can save hours of debugging — and maybe even your next production incident.
Suggested Diagram Sections for the Article
- Request → Spring Proxy → Transaction Manager → Database
- Self-invocation vs Proxy invocation
- Transaction lifecycle (BEGIN → Business Logic → COMMIT/ROLLBACK)
- Propagation modes comparison
- Isolation levels visualized
- Single database transaction vs microservices with Saga/Outbox
- Common mistakes checklist
If this article helped you understand what really happens behind
@Transactional, give it a 👏, share it with your team, and follow me for deep dives into Java, Spring Boot, JVM internals, high-performance backend systems, and production engineering. The best developers don't just use annotations—they understand what happens underneath.
메타데이터
- post_id
- aabe03ed1cf0
- slug
- i-found-transactional-on-400-methods-in-production-half-of-them-were-unnecessary-aabe03ed1cf0
- url
- https://medium.com/@hiteshdhamshaniya-wvmagic/i-found-transactional-on-400-methods-in-production-half-of-them-were-unnecessary-aabe03ed1cf0
- canonical_url
- https://medium.com/@hiteshdhamshaniya-wvmagic/i-found-transactional-on-400-methods-in-production-half-of-them-were-unnecessary-aabe03ed1cf0
- author_url
- https://medium.com/@hiteshdhamshaniya-wvmagic
- status
- ok
- fetched_at
- 2026-07-28 18:11:31