How does the execute(TransactionCallback) form of TransactionalOperator differ from transactional(Mono/Flux), and how do you force a rollback on an otherwise-successful flow?
answer
- execute → callback gets ReactiveTransaction status
- setRollbackOnly() = rollback without an error
- execute always returns Flux
- transactional = concise decorator
- business-rule abort on a successful flow
basics
~20 stransactional() just wraps an existing Mono/Flux. execute() takes a callback that receives the ReactiveTransaction handle, so you can inspect it and call setRollbackOnly() to force a rollback even when the pipeline completes successfully. execute() returns a Flux.
solid answer
~40 s`transactional(Mono)`/`transactional(Flux)` simply decorate a publisher you already built and give commit-on-complete / rollback-on-error. `execute(TransactionCallback<T>)` is the lower-level form: your callback receives the `ReactiveTransaction` status object and returns a `Publisher<T>`; `execute` always returns a `Flux<T>`. The key extra power is access to that status handle — you can call `reactiveTransaction.setRollbackOnly()` to mark the transaction for rollback even though the pipeline completes normally (no error signal). This is how you roll back on a *business* condition — e.g. a validation that returns a value but must not persist. You can also read status like `isNewTransaction()`. Both forms share the same commit/rollback-on-signal semantics and both run within the Reactor Context. Choose `execute` when you need the status handle; otherwise `transactional` is more concise.
code
java · 13 linesimport org.springframework.transaction.ReactiveTransaction;
import org.springframework.transaction.reactive.TransactionalOperator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public Mono<Report> auditedDryRun(TransactionalOperator rxtx) {
Flux<Report> flux = rxtx.execute((ReactiveTransaction status) ->
auditRepo.save(new AuditEntry("dry-run started"))
.then(reportService.build())
// completes successfully, but we do NOT want to persist the audit row
.doOnNext(report -> status.setRollbackOnly()));
return flux.next(); // execute() returns Flux; take the single Report (outside the wrap)
}go deeper
Just know execute takes a callback and transactional wraps a publisher.
Know setRollbackOnly() forces a rollback without an error signal.
Explain the ReactiveTransaction status handle, the Flux return type, and correct timing/placement of setRollbackOnly().
Frame use cases (saga aborts, dry-runs, policy checks) and the discipline of keeping stream-shape adapters outside the transactional boundary.
## Two entry points `TransactionalOperator` exposes two usage styles: 1. **Decorator style** — `Mono<T> transactional(Mono<T>)` and `Flux<T> transactional(Flux<T>)`. You already have a publisher; you wrap it. Terminal signal decides commit/rollback. Most concise and most common. 2. **Callback style** — `<T> Flux<T> execute(TransactionCallback<T> action)`. `TransactionCallback<T>` is a functional interface: `Publisher<T> doInTransaction(ReactiveTransaction status)`. Spring starts the transaction, hands you the **`ReactiveTransaction` status object**, and subscribes to whatever `Publisher` you return. Note `execute` always returns a **`Flux<T>`**, even if your callback returns a `Mono`. ## Why the status handle matters The `ReactiveTransaction` (subtype of `TransactionExecution`) lets you: - `setRollbackOnly()` — mark the transaction so it will roll back **even if the pipeline completes without an error signal**. This is the reactive equivalent of `TransactionStatus.setRollbackOnly()` in the imperative `TransactionTemplate`/`TransactionCallback`. - `isRollbackOnly()`, `isNewTransaction()`, `isCompleted()` — inspect state. This solves a real problem: sometimes a flow **succeeds** (emits a value, completes) but a **business rule** says the work must not persist — e.g. a dry-run, a policy check that returns a report, or a saga step that decides to abort. With only `transactional(Mono)` you'd have to manufacture an artificial error to force rollback; `setRollbackOnly()` is the clean way. ## Example contrast ```java // Decorator: commit unless the pipeline errors Mono<Report> a = rxtx.transactional(buildReport()); // Callback: commit only if the report is 'clean', else roll back the audit rows we wrote Flux<Report> b = rxtx.execute(status -> auditRepo.save(entry) .then(buildReport()) .doOnNext(r -> { if (r.hasViolations()) status.setRollbackOnly(); })); ``` In `b`, even though the chain completes normally, the audit rows are rolled back when violations are found. ## Semantics shared by both - Commit on `onComplete`, rollback on `onError` or `cancel`. - Transaction lives in the Reactor Context; only reactive data access enlists. - Nothing happens until subscription. ## Gotchas - `execute` returning `Flux<T>` means if you expected a `Mono`, add `.next()`/`Mono.from(...)` downstream (careful: `.next()` cancels the rest of a multi-element Flux, which would roll back if placed inside the wrap — keep it outside). - `setRollbackOnly()` must be called **before** the terminal completion is processed — do it inside the pipeline (e.g. in a `doOnNext`/`flatMap`), not after the operator has already committed. - Do not confuse `ReactiveTransaction` with the imperative `TransactionStatus`; they are parallel types in `org.springframework.transaction`.
- What return type does execute() give and why does that matter?Always Flux<T>, even for a single result. You often need .next() or Mono.from() to adapt it — and you must place that adaptation outside the transactional boundary so it doesn't cancel/roll back the work.
- When must setRollbackOnly() be called to take effect?While the pipeline is still running, before the terminal onComplete is processed by the operator — typically inside a doOnNext/flatMap. Once committed it's too late.
saying these in an interview costs you the question
- Thinking you must throw an exception to roll back a successful flow
- Believing execute() returns a Mono
- Calling setRollbackOnly() after the operator has already committed and expecting it to work