skip to content

What is GenericReactiveTransaction and what role does it play in the ReactiveTransactionManager contract?

level: seniorimportance: should knowfreq 28%

answer

  1. default impl of ReactiveTransaction handle
  2. reactive twin of DefaultTransactionStatus
  3. isNewTransaction / setRollbackOnly / isCompleted
  4. passed back to commit/rollback
  5. created by AbstractReactiveTransactionManager, not you

basics

~10 s

GenericReactiveTransaction is the default implementation of the ReactiveTransaction handle returned by getReactiveTransaction. It represents one running reactive transaction (new-or-existing, rollback-only flag, completion state) and is passed back to commit or rollback.

solid answer

~40 s

ReactiveTransactionManager.getReactiveTransaction returns Mono<ReactiveTransaction>; GenericReactiveTransaction is the default concrete implementation of that ReactiveTransaction handle — the reactive analogue of DefaultTransactionStatus in the blocking world. It carries the transaction's metadata and control flags: whether it is a new transaction, whether it is nested/has a savepoint, whether it is marked rollback-only (setRollbackOnly/isRollbackOnly), whether it already completed, and it holds the underlying transaction object (e.g. the R2DBC connection holder) internally. You pass this same handle to commit or rollback so the manager knows what to finalize. Application code rarely touches it directly — Spring's TransactionInterceptor and TransactionalOperator create and pass it around. You mainly encounter it when writing a custom ReactiveTransactionManager or inspecting rollback-only propagation semantics.

code

java · 9 lines
java
// The handle flows through the contract; app code rarely holds it.
ReactiveTransactionManager tm = new R2dbcTransactionManager(cf);

Mono<Void> manual = tm.getReactiveTransaction(new DefaultTransactionDefinition())
    .flatMap(tx -> doWork()                          // tx is a GenericReactiveTransaction
        .then(tm.commit(tx))                          // commit same handle...
        .onErrorResume(ex -> tm.rollback(tx)          // ...or roll it back
            .then(Mono.error(ex))));
// In practice TransactionalOperator / @Transactional do exactly this for you.

go deeper

for a junior

Recognize it as the handle returned when a reactive transaction starts.

for a middle

Know its flags (new, rollback-only, completed) and that it's passed to commit/rollback.

for a senior

Explain rollback-only propagation and the AbstractReactiveTransactionManager relationship.

for a principal

Discuss authoring a custom ReactiveTransactionManager and propagation semantics via the handle.

## Where it sits in the contract Recall the interface: ```java public interface ReactiveTransactionManager extends TransactionManager { Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition def); Mono<Void> commit(ReactiveTransaction transaction); Mono<Void> rollback(ReactiveTransaction transaction); } ``` `ReactiveTransaction` is the *handle* to a live transaction. **`GenericReactiveTransaction`** (in `org.springframework.transaction.reactive`) is its **default implementation**, created by `AbstractReactiveTransactionManager` when it starts or resolves a transaction. It is the reactive twin of **`DefaultTransactionStatus`** / `TransactionStatus` from the imperative side. ## What it holds `ReactiveTransaction` (extends `TransactionExecution`) exposes: - **`isNewTransaction()`** — did this call *create* a new transaction, or join an existing one (propagation)? - **`setRollbackOnly()` / `isRollbackOnly()`** — mark the transaction so it can *only* roll back; used to propagate failure intent through nested participants. - **`isCompleted()`** — has commit/rollback already run? - **(nested)** whether a savepoint is held for nested propagation. `GenericReactiveTransaction` additionally wraps the **underlying transaction object** — for `R2dbcTransactionManager` that is the connection holder bound into the Reactor Context — plus flags like `newSynchronization` and whether it is a `nested` transaction with a savepoint. ## The lifecycle 1. `getReactiveTransaction(def)` runs propagation logic and returns a `Mono<ReactiveTransaction>` emitting a `GenericReactiveTransaction`. If a transaction already exists and propagation says *join*, `isNewTransaction()` is false. 2. Your work executes, participating via the Context-bound connection. 3. On success, `commit(tx)` is called with **the same handle**; the manager checks `isRollbackOnly()` — if set, it rolls back instead (and may throw `UnexpectedRollbackException`), otherwise commits. On error, `rollback(tx)` is called. 4. The handle's `isCompleted()` flips true. ## Rollback-only propagation Because the handle carries `rollbackOnly`, an inner participant that fails can `setRollbackOnly()`; when the outer boundary tries to commit, the manager sees the flag and forces a rollback — the reactive equivalent of the classic "marked rollback-only" behavior. This is central to nested propagation semantics. ## Who creates and uses it - **`AbstractReactiveTransactionManager`** creates it (subclasses like `R2dbcTransactionManager` supply the resource-specific bits via `doGetTransaction`, `doBegin`, `doCommit`, `doRollback`). - **`TransactionInterceptor`** (for `@Transactional`) and **`TransactionalOperator`** obtain it and thread it through commit/rollback. - **Application code** almost never handles it directly. You touch it when authoring a **custom `ReactiveTransactionManager`** or when reasoning about propagation/rollback-only edge cases. ## Gotchas - Don't confuse it with `GenericReactiveTransaction` being something you *new up* yourself — it's an SPI detail managed by the framework. - `isNewTransaction()` matters for propagation: only the *outermost* new transaction actually commits/rolls back physically; joined participants defer to it. - Setting `setRollbackOnly()` does **not** immediately roll back — it marks intent; the physical rollback happens at the boundary.

  • What is the imperative-world equivalent of GenericReactiveTransaction?
    TransactionStatus / DefaultTransactionStatus. Both are the handle returned when a transaction starts and passed back to commit or rollback, carrying isNewTransaction, rollback-only, and completion state.
  • Does calling setRollbackOnly() on the handle immediately roll back the transaction?
    No. It only marks intent. The physical rollback happens when the transaction boundary attempts to commit and the manager sees the rollback-only flag, then rolls back (and may raise UnexpectedRollbackException).

saying these in an interview costs you the question

  • Saying you instantiate GenericReactiveTransaction in application code
  • Claiming setRollbackOnly rolls back immediately
  • Confusing it with the ReactiveTransactionManager itself
  • Not knowing it is the analogue of TransactionStatus

context