2 @Transactional Traps That Catch Even Senior Java Developers in interviews

작성자

카테고리:

← 피드로
DEV Community · Nikhil Kamani · 2026-07-08 개발(SW)
Cover image for 2 @Transactional Traps That Catch Even Senior Java Developers in interviews

Nikhil Kamani

I have 13 years of Java experience. I have also interviewed hundreds of developers at MNCs.

Two questions about @Transactional that catch even senior developers. Try answering before you scroll down.

Q1: If a non-transactional method calls a @Transactional method in the same class, does the transaction apply?

@Service
public class OrderService {

    public void placeOrder(Order order) {
        this.saveOrder(order);
    }

    @Transactional
    public void saveOrder(Order order) {
        orderRepository.save(order);
        inventoryRepository.update(order);
    }
}

Enter fullscreen mode Exit fullscreen mode

Answer: No.

Reason, in plain terms: when you add @Transactional, Spring does not add the transaction logic inside your class. Instead, it creates a second object that sits in front of your class — think of it as a wrapper. Other classes that call saveOrder() (through @Autowired) actually call this wrapper first. The wrapper starts the transaction, then calls your real method, then commits or rolls back.

This wrapper only exists outside your class. So when your own code calls this.saveOrder(order), it skips the wrapper completely and goes straight to the real method. No wrapper means no transaction logic. No rollback if inventoryRepository.update() fails.

This bug never shows up in tests. It only shows up in production, when a failure leaves your data half-updated.

Fix: call the method from a different class, or inject the wrapper of your own class:

@Service
public class OrderService {

    @Autowired
    private OrderService self; // this is the wrapper, not the raw object

    public void placeOrder(Order order) {
        self.saveOrder(order); // now it goes through the wrapper
    }

    @Transactional
    public void saveOrder(Order order) {
        orderRepository.save(order);
        inventoryRepository.update(order);
    }
}

Enter fullscreen mode Exit fullscreen mode

Q2: Does @Transactional roll back on every exception?

@Transactional
public void processRefund(Order order) throws RefundException {
    paymentRepository.markRefunded(order);
    externalRefundService.notify(order); // throws RefundException
}

Enter fullscreen mode Exit fullscreen mode

Answer: No.

Reason: by default, @Transactional only rolls back on unchecked exceptions (RuntimeException and its subclasses). RefundException above is a checked exception. So if it is thrown, Spring lets the transaction commit anyway — including the markRefunded write that happened right before the failure.

Fix: tell Spring to roll back on any exception, not just unchecked ones:

@Transactional(rollbackFor = Exception.class)
public void processRefund(Order order) throws RefundException {
    paymentRepository.markRefunded(order);
    externalRefundService.notify(order);
}

Enter fullscreen mode Exit fullscreen mode

I cover topics like this in more depth in my guide — Core Java, Java 8 to 21, Multithreading, Spring Boot, Microservices, Design Patterns, and Coding Round Patterns.

Free sample: https://drive.google.com/file/d/1u3PQbTY1gLn34UmWG7Cxx4cmdibD2dvU/view?usp=sharing

Full guide: https://kamaninikhil.gumroad.com/l/java-interview-guide

Got either one right? Drop it in the comments.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다