Sitemap
Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.

đŸ’„ The Truth About @Transactional — Why Your Rollbacks Don’t Actually Work (2025 Guide)

4 min readNov 2, 2025

--

đŸ˜© The Pain You’ve Felt Before

You’ve got a clean Spring Boot service:

@Transactional
public void createOrder() {
saveOrder();
savePayment(); // oops, exception here
}

You expect it to roll back, right?
Then you check your database


👀 The order table still has data.
đŸ˜± The rollback didn’t happen.

You restart your app, recheck your config, even blame JPA.
But here’s the truth — @Transactional didn’t fail. You just misunderstood it.

Let’s decode what’s really happening inside Spring’s transaction magic.

⚙ What @Transactional Actually Does

When you annotate a method with @Transactional, Spring doesn’t “magically” make rollbacks happen.

It does something more subtle:

It wraps your method inside a proxy, and that proxy manages the transaction lifecycle.

đŸ§© In plain English:

  • Before your method executes → starts a transaction
  • If method completes normally → commits
  • If method throws a RuntimeException → rolls back

Sounds simple, right?
Well
 here’s where most developers get trapped.

💣 The 7 Deadly @Transactional Misconceptions

1ïžâƒŁ You’re Calling a Transactional Method from the Same Class

The #1 rookie mistake — self-invocation.

@Service
public class OrderService {

@Transactional
public void placeOrder() {
saveOrder();
notifyCustomer();
}
    public void notifyCustomer() {
sendEmail();
throw new RuntimeException("Email failed!");
}
}

Here’s the catch 👇
placeOrder() calls notifyCustomer() inside the same class.

But Spring creates transactions through proxies — and internal method calls don’t go through the proxy.

đŸ’„ Result: @Transactional is ignored completely.

✅ Fix: Move notifyCustomer() to another bean, or call via AopContext:

((OrderService) AopContext.currentProxy()).notifyCustomer();

2ïžâƒŁ You’re Throwing the Wrong Exception

By default, Spring only rolls back for unchecked exceptions (i.e. subclasses of RuntimeException).

So this won’t roll back 👇

@Transactional
public void process() throws Exception {
throw new Exception("Checked exception");
}

✅ Fix:
Specify rollback rules manually:

@Transactional(rollbackFor = Exception.class)
public void process() throws Exception {
throw new Exception("Now it rolls back!");
}

💡 Rule of thumb:

  • Runtime exceptions → rollback automatically
  • Checked exceptions → only rollback if you tell Spring to

3ïžâƒŁ Your Transactional Method Isn’t public

This one’s sneaky.

Spring’s AOP proxies only intercept public methods by default.
So if you’ve written:

@Transactional
private void doWork() { ... }


it silently does nothing.

Get Dolly’s stories in your inbox

Join Medium for free to get updates from this writer.

✅ Fix: Always make transactional methods public.

4ïžâƒŁ You’re Mixing Different Transaction Managers

If you’re using multiple datasources (say one MySQL + one MongoDB) and you annotate:

@Transactional
public void syncData() {
mysqlRepo.save(...);
mongoRepo.save(...);
}

Only one of them actually participates in the transaction — the other won’t roll back.

✅ Fix: Specify your transaction manager:

@Transactional("mysqlTransactionManager")

Or use a distributed transaction manager (like Atomikos or Narayana).

5ïžâƒŁ You’re Using REQUIRES_NEW or NESTED Incorrectly

Spring supports propagation behaviors, like:

  • REQUIRED → joins existing transaction (default)
  • REQUIRES_NEW → always starts a new transaction
  • NESTED → creates savepoints

Example:

@Transactional
public void createOrder() {
saveOrder();
try {
saveAudit();
} catch (Exception e) {
log.error("Audit failed");
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAudit() {
throw new RuntimeException("Audit failed");
}

Result:

  • saveAudit() rolls back its own transaction.
  • But main transaction (createOrder) still commits.

💡 Lesson: Propagation settings control rollback boundaries — use carefully.

6ïžâƒŁ You’re Not Using the Right PlatformTransactionManager

If you’re using JPA, make sure your config uses the JpaTransactionManager, not DataSourceTransactionManager.

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}

Otherwise, Hibernate flush behavior might be inconsistent — partial commits, no rollback, and ghost data. đŸ‘»

7ïžâƒŁ Your Method Is Not in a Spring-Managed Bean

Spring transactions only work when the object is managed by Spring’s container.

So if you’re manually creating objects like:

OrderService service = new OrderService();
service.createOrder();

Spring can’t intercept that call → no transaction.

✅ Fix: Always inject via Spring (@Autowired, constructor, or bean).

🔬 Bonus Tip: Verify It’s Working

You can always check the transaction status programmatically:

TransactionStatus status = 
TransactionAspectSupport.currentTransactionStatus();
System.out.println(status.isNewTransaction());

Or enable debug logs in application.yml:

logging:
level:
org.springframework.transaction: TRACE

You’ll literally see when transactions start, commit, or roll back.

đŸ§© TL;DR — The Transaction Truth Table

🧠 Final Thoughts

@Transactional isn’t broken — it’s just honest.

It works exactly as designed, but only if you understand how Spring AOP and propagation work together.

The next time your rollback doesn’t happen, don’t panic — check the rules above. Chances are, it’s not a bug. It’s a proxy. 😉

A message from our Founder

Hey, Sunil here. I wanted to take a moment to thank you for reading until the end and for being a part of this community.

Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? We don’t receive any funding, we do this to support the community. ❀

If you want to show some love, please take a moment to follow me on LinkedIn, TikTok, Instagram. You can also subscribe to our weekly newsletter.

And before you go, don’t forget to clap and follow the writer!

--

--

Dolly
Dolly

Written by Dolly

Java & Spring Boot Developer | Writing about Backend Development, Microservices, and Scalable System Design. Helping developers build production-ready APIs.

Stackademic
Stackademic

Published in Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.