Catch @Transactional Pitfalls in Spring Boot
Catches @Transactional calls that bypass the Spring proxy, checked exceptions that commit instead of rolling back, and remote calls inside transactions.
What it flags
@Service
public class OrderService {
private final OrderRepository orders;
private final PaymentClient payments;
public OrderService(OrderRepository orders, PaymentClient payments) {
this.orders = orders;
this.payments = payments;
}
public Order checkout(CheckoutRequest request) throws PaymentDeclinedException {
return createOrder(request); // self-call skips the proxy, so no transaction starts
}
@Transactional
public Order createOrder(CheckoutRequest request) throws PaymentDeclinedException {
Order order = orders.save(Order.pending(request));
payments.charge(order.getId(), request.paymentToken());
order.markPaid();
return order;
}
}@Service
public class OrderService {
private final OrderWriter writer; // separate bean with short @Transactional methods
private final PaymentClient payments;
public OrderService(OrderWriter writer, PaymentClient payments) {
this.writer = writer;
this.payments = payments;
}
public Order checkout(CheckoutRequest request) throws PaymentDeclinedException {
Order order = writer.createPending(request);
try {
payments.charge(order.getId(), request.paymentToken());
} catch (PaymentDeclinedException e) {
writer.markFailed(order.getId());
throw e;
}
return writer.markPaid(order.getId());
}
}Why it matters
A self-invoked @Transactional method runs without a transaction, so a failure halfway through leaves earlier writes committed, and entity changes made after a repository call may never be written. A checked exception thrown from a method that looks transactional commits whatever it already wrote. Both read as correct in a diff because the annotation is right there, and unit tests that mock the repository cannot tell the difference. Remote calls inside a transaction hold a pooled connection and any row locks for the length of the call, so one slow downstream service can exhaust the connection pool and stall unrelated endpoints.
Agent instructions
cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.
AGENTS.md
Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.
## Catch @Transactional Pitfalls in Spring Boot Applies to `**/*.java`. Review Spring service code for `@Transactional` boundaries that never start, do not roll back, or hold database connections longer than needed. Flag changes that: - Call a `@Transactional` method from another method of the same class (`this.save(...)` or an unqualified call). The call skips the Spring proxy, so attributes such as `REQUIRES_NEW`, `readOnly`, or `rollbackFor` never apply. - Put `@Transactional` on private or final methods, or call `@Transactional` methods on objects created with `new`, where no Spring proxy intercepts the call. - Throw checked exceptions from a `@Transactional` method that should roll back on failure, without `rollbackFor` (or `rollbackOn` for `jakarta.transaction.Transactional`) covering them. By default only `RuntimeException` and `Error` roll back. - Catch and continue after a runtime exception from an inner `@Transactional` call on another bean that joined the same transaction. The transaction is already marked rollback-only, and the outer commit throws `UnexpectedRollbackException`. - Make HTTP calls, call other services, or send email inside a transaction, holding a pooled connection and row locks for the length of the remote call. - Send messages or publish events that other systems act on before the transaction commits, instead of using `@TransactionalEventListener` or an outbox table. Do not flag: - Self-calls from a method that already runs in a transaction when the callee declares no different attributes. - Checked exceptions on flows that are meant to commit, or projects that configure rollback for all exceptions globally. - Code that uses AspectJ weaving (`AdviceMode.ASPECTJ`), where self-calls are advised.
Checks files matching **/*.java.
More Spring Boot agents
See all Spring Boot code review standards
Run this agent on every pull request
cubic reviews every pull request against the agents you turn on and comments on the lines that break them.