← Back to list

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)

Hitesh Laxman · 2026-07-17 18:45 · 70 claps · 5.1 min read paywalled
#transactional #spring-boot #software-engineering #application #java
Open on Medium ↗

I Found @Transactional on 400+ Methods in Production. Half of Them Were Unnecessary.

Hitesh Laxman Transactional Method Spring Boot Application Development

Hitesh Laxman Transactional Method Spring Boot Application Development

Your @Transactional Method Might Not Be Working (And You Probably Don't Know Why)

Friendly Link

  1. I Found @Transactional on 400+ Methods in Production. Half of Them Were Unnecessary.
  2. The Hidden Cost of @Transactional That Most Spring Boot Developers Ignore
  3. Why Your @Transactional Isn't Working (Even Though There Are No Errors)
  4. Stop Putting @Transactional Everywhere. Here's What Really Happens.
  5. 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 @Transactional does 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

  1. Request → Spring Proxy → Transaction Manager → Database
  2. Self-invocation vs Proxy invocation
  3. Transaction lifecycle (BEGIN → Business Logic → COMMIT/ROLLBACK)
  4. Propagation modes comparison
  5. Isolation levels visualized
  6. Single database transaction vs microservices with Saga/Outbox
  7. 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