venkatesh
№'014' · MAY 2026 · 3 MIN READ

'@Transactional Was There. Rollback Wasn''t.'

@Transactional Was There. Rollback Wasn't.

@Transactional was right there. Rollback still didn’t happen. 💀

The flow: user completes payment → Order, Payment, and Inventory all update in one method. If one fails, rollback everything.

Inventory update failed midway. The other two tables? Already committed. Inconsistent data sitting in prod, no rollback, no errors — just silent corruption. 💥

![The problem: two tables committed, third failed — first two already committed](/images/transactional-no-rollback-1.png)

Why didn’t rollback happen?

We were calling the @Transactional method from another method in the same class.

@Service
public class MyService {

    public void outerMethod() {
        // some logic
        this.innerTransactionalMethod(); // internal call
    }

    @Transactional
    public void innerTransactionalMethod() {
        // updates 3 tables
    }
}

It ran like a regular method. No transaction. No rollback.

![Why rollback didn’t happen: calling @Transactional from same class](/images/transactional-no-rollback-2.png)

How Spring @Transactional actually works

Spring’s @Transactional works through proxies — think of it as a wrapper around your bean from the outside.

  • External call → goes through the Spring proxy → transaction starts ✅
  • Internal call (this.method()) → bypasses the proxy entirely ❌

So our “transactional” method was just a plain method call with no transaction context around it. Self-invocation is the real culprit.

![How Spring proxy works: external vs internal call](/images/transactional-no-rollback-3.png)

The fix

Move the @Transactional logic into a separate bean and call it externally:

// Orchestrator — no @Transactional
@Service
public class MyService {
    @Autowired
    private TxService txService;

    public void outerMethod() {
        txService.innerTransactionalMethod(); // external call → proxy → ✅
    }
}

// Separate bean — @Transactional works correctly here
@Service
public class TxService {
    @Transactional
    public void innerTransactionalMethod() {
        // updates 3 tables — now properly wrapped in a transaction
    }
}

One small refactor. Rollback started working again. ✅

![The fix: move @Transactional to a separate bean](/images/transactional-no-rollback-4.png)

@Transactional doesn’t protect you from where you call it from. The annotation is only as good as the proxy that wraps it.

copied!