What is TransactionStatus in Spring's programmatic transaction API, and what does setRollbackOnly() do?
answer
- handle returned by getTransaction()
- setRollbackOnly = one-way doom switch
- commit later -> forced rollback
- rollback without throwing
- currentTransactionStatus() to reach it declaratively
basics
~10 sTransactionStatus is a handle to the current transaction that PlatformTransactionManager gives you. Calling setRollbackOnly() marks the transaction so it can only roll back — commit will not succeed.
solid answer
~40 sWhen you begin a transaction programmatically via PlatformTransactionManager.getTransaction(...), it returns a TransactionStatus object. This is your control handle for that transaction: you inspect it and steer the outcome. The key method is setRollbackOnly(), which flags the transaction as 'rollback-only' — a one-way switch. Once set, any later commit attempt on that logical transaction is forced to roll back instead. This lets code decide to abort without throwing an exception. You'd call it in a business branch where you detect an invalid state but want to keep control flow. With the higher-level TransactionTemplate you achieve the same by calling status.setRollbackOnly() inside execute(). It complements the declarative @Transactional approach, where an unchecked exception normally triggers rollback automatically.
code
java · 19 lines// Programmatic style
PlatformTransactionManager txManager; // injected
public void transfer(Account from, Account to, BigDecimal amount) {
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
try {
from.debit(amount);
if (from.getBalance().signum() < 0) {
// decide to abort without throwing
status.setRollbackOnly();
} else {
to.credit(amount);
}
txManager.commit(status); // will roll back if rollbackOnly was set
} catch (RuntimeException ex) {
txManager.rollback(status);
throw ex;
}
}go deeper
Know that TransactionStatus is the handle from getTransaction() and setRollbackOnly() forces a rollback at commit.
Know the checked-vs-unchecked rollback default and the currentTransactionStatus() declarative shortcut.
Explain why an inner setRollbackOnly leads to UnexpectedRollbackException in the outer commit.
Weigh setRollbackOnly vs throwing for control flow, and its interaction with propagation and participating transactions.
## Programmatic transactions Spring offers two styles of transaction management: - *declarative* (`@Transactional` annotations) - and *programmatic* (you call the API yourself). The core interface is `PlatformTransactionManager`, with the single method `TransactionStatus getTransaction(TransactionDefinition definition)`, plus `commit(TransactionStatus)` and `rollback(TransactionStatus)`. ## TransactionStatus When you start a transaction, `getTransaction(...)` hands back a `org.springframework.transaction.TransactionStatus`. Think of it as a *ticket/handle* representing the transaction currently in progress. You pass it back to `commit()` or `rollback()`, and you can query/steer it in the meantime. `TransactionStatus` extends: - `TransactionExecution` (which exposes `isNewTransaction()`, `isRollbackOnly()`, `isCompleted()`) - and `SavepointManager` (savepoint methods, covered separately). ## setRollbackOnly() This method marks the transaction as *rollback-only*. It is a **one-way, sticky flag**: once set it cannot be cleared. The effect is that when someone eventually calls `commit()` on that logical transaction, Spring will **not** commit — it rolls back instead. Use it when your code has decided the unit of work must be abandoned, but you don't want to (or can't) throw an exception to signal that. ## Why not just throw? With declarative `@Transactional`, an escaping *unchecked* exception (a `RuntimeException` or `Error`) triggers automatic rollback; checked exceptions do **not** roll back by default. `setRollbackOnly()` gives you rollback *without* an exception and independent of the checked/unchecked rule — handy when the abort is a normal business branch, or when you catch an exception, log it, and still want the transaction doomed. ## Declarative equivalent Inside an `@Transactional` method you can reach the same flag without holding the status object: - `TransactionInterceptor.currentTransactionStatus().setRollbackOnly();` - or, more commonly, `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();`. ## Gotcha — silent participation If an *inner* method marks the transaction rollback-only and the *outer* method then tries to commit, Spring throws `UnexpectedRollbackException` at commit time to tell the caller 'you asked to commit but the transaction was already doomed.' That surprises people because the code that set the flag ran without error. ## When to use Prefer declarative `@Transactional` for the common case. Reach for `TransactionStatus.setRollbackOnly()` when you need fine-grained, in-method control: - multi-step programmatic flows, - conditional aborts, - or when integrating with `TransactionTemplate`.
- Inside a plain @Transactional method, how do you mark the current transaction rollback-only without holding a TransactionStatus reference?Call TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() (or TransactionInterceptor.currentTransactionStatus()). It resolves the status bound to the current thread by the transaction interceptor.
- Does a checked exception thrown from a @Transactional method roll back by default?No. By default only unchecked exceptions (RuntimeException/Error) trigger rollback. Checked exceptions commit unless you configure rollbackFor, or you explicitly call setRollbackOnly().
saying these in an interview costs you the question
- Thinking setRollbackOnly() immediately ends/aborts the transaction (it only sets a flag; rollback happens at commit time)
- Believing the flag can be reset to false later
- Assuming all exceptions cause rollback (checked ones don't, by default)