How @Transactional Works in Spring
Fast recall
Quick revision
Core answer
@Transactional declares a transaction boundary. With the usual proxy-based setup, Spring intercepts a call through the bean proxy, asks the configured transaction manager to begin or join a transaction, runs the method, and then commits or rolls back. By default, RuntimeException and Error cause rollback; checked exceptions do not unless the rollback rules are changed.Understanding the transaction boundary, rollback rules, propagation, and the difference between a call through a Spring proxy and self-invocation.
Assuming an inner `@Transactional` annotation is applied during self-invocation. In proxy mode the inner call is not intercepted, although an outer transaction may already be active.
Explain that method visibility depends on proxy type and Spring version: private methods cannot be intercepted, while class-based proxies can support protected or package-visible methods in current Spring versions.
Interview-ready guide
Interview answer
@Transactional declares that a Spring-managed operation should run inside a transaction.
In the usual proxy-based setup, the proxy asks the configured transaction manager to begin a new transaction or join one, calls the method, and then commits or rolls back. By default, RuntimeException and Error cause rollback; checked exceptions need an explicit rollback rule when that is the desired behavior. A service method that saves an order and updates inventory is a good boundary because both database changes must agree.
Self-invocation does not apply the inner annotation because the call misses the proxy, although an outer transaction may already exist. Private methods cannot be proxied, and support for other non-public methods depends on proxy type. readOnly = true is an intent hint, not a guaranteed optimization or write lockout.
How @Transactional Works in Spring
Direct Answer
@Transactional declares a transaction boundary. With the usual proxy-based setup, Spring intercepts a call through the bean proxy, asks the configured transaction manager to begin or join a transaction, runs the method, and then commits or rolls back. By default, RuntimeException and Error cause rollback; checked exceptions do not unless the rollback rules are changed.
Code Example
@Service
public class OrderService {
@Transactional
public Order createOrder(OrderRequest request) {
var order = orderRepo.save(new Order(request.customerId()));
inventoryRepo.reserveIfAvailable(request.items());
return order;
}
@Transactional(readOnly = true) // intent hint; effect depends on manager/provider
public Optional<Order> findOrder(Long orderId) {
return orderRepo.findById(orderId);
}
}
This forms one transaction only when both repositories use the participating transactional resource. A default rollback exception must escape for the first method to roll back automatically.
Common Mistakes & Pitfalls
- Misreading self-invocation: A same-object call bypasses the proxy, so the inner annotation is ignored. An outer transaction may still be active; the mistake is claiming that no transaction can exist at all.
- Using a private method: A proxy cannot intercept a private method. Current class-based proxies can support protected or package-visible methods, while interface-based proxies require calls through the interface's public methods.
- Assuming every exception rolls back: By default,
RuntimeExceptionandErrorcause rollback. ConfigurerollbackForwhen a checked exception should roll back.
Check your understanding
Answer these without looking back at the article.
- What is transaction propagation in Spring?
- Why doesn't @Transactional work on private methods?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.