What is ReactiveTransactionManager and how does it differ from PlatformTransactionManager?
answer
- Two interfaces: Platform (blocking) vs Reactive (Mono)
- getReactiveTransaction/commit/rollback return Mono
- R2dbcTransactionManager over ConnectionFactory
- state in Reactor Context, not ThreadLocal
- @Transactional picks path by return type
basics
~10 sReactiveTransactionManager runs transactions for non-blocking code, returning Mono results instead of blocking. PlatformTransactionManager is the classic blocking one for imperative code (JDBC/JPA). You pick the one matching your stack.
solid answer
~40 sSpring has two transaction-manager abstractions. PlatformTransactionManager is the imperative, blocking one (DataSourceTransactionManager, JpaTransactionManager); its methods return synchronously and it holds transaction state in ThreadLocal. ReactiveTransactionManager is its non-blocking sibling for reactive stacks: getReactiveTransaction, commit, and rollback all return Mono, so nothing blocks the event-loop thread. Its main implementation is R2dbcTransactionManager over an R2DBC ConnectionFactory. The key difference beyond return types is where transaction state lives: reactive managers carry it in the Reactor Context rather than a thread-bound ThreadLocal, so the transaction survives when work hops between threads. @Transactional works with either — Spring inspects whether the method returns a reactive type (Mono/Flux) to choose the reactive path.
code
java · 16 lines@Configuration
@EnableTransactionManagement
class TxConfig {
// Reactive: state carried in the Reactor Context
@Bean
ReactiveTransactionManager reactiveTx(ConnectionFactory cf) {
return new R2dbcTransactionManager(cf);
}
// Imperative equivalent (for contrast): state in ThreadLocal
@Bean
PlatformTransactionManager blockingTx(DataSource ds) {
return new DataSourceTransactionManager(ds);
}
}go deeper
Know there are two managers and reactive one returns Mono, not blocks.
Explain state-in-Context vs ThreadLocal and that @Transactional chooses by return type.
Discuss R2dbcTransactionManager wiring, why bridging blocking/reactive fails, void-return pitfall.
Reason about mixed stacks, migration strategy, and where reactive tx boundaries belong in an event-loop architecture.
## The two abstractions Spring's transaction infrastructure has two top-level interfaces: - **`PlatformTransactionManager`** — the original, *imperative/blocking* abstraction. Implementations include `DataSourceTransactionManager` (plain JDBC), `JpaTransactionManager` (JPA/Hibernate), and `JtaTransactionManager`. Its methods (`getTransaction`, `commit`, `rollback`) run synchronously and *block* the calling thread until the database responds. - **`ReactiveTransactionManager`** — the *non-blocking* abstraction introduced in Spring 5.2 for reactive stacks (WebFlux + R2DBC). Its methods return `reactor.core.publisher.Mono`, so calling them schedules work without blocking the caller's thread. Both extend the marker interface `TransactionManager`. ## The ReactiveTransactionManager API ```java public interface ReactiveTransactionManager extends TransactionManager { Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition); Mono<Void> commit(ReactiveTransaction transaction); Mono<Void> rollback(ReactiveTransaction transaction); } ``` The returned `ReactiveTransaction` (default impl `GenericReactiveTransaction`) is the reactive analogue of `TransactionStatus`. ## The main implementation `org.springframework.r2dbc.connection.R2dbcTransactionManager` drives transactions over an R2DBC `ConnectionFactory` (the reactive equivalent of a JDBC `DataSource`). You construct it with a `ConnectionFactory`. Spring Data R2DBC / `DatabaseClient` participate in the transaction it starts. ## The critical conceptual difference: where state lives In the blocking world, transaction state (the current transaction, bound connection, synchronizations) is stored in **`ThreadLocal`** via `TransactionSynchronizationManager`. That works because a blocking call keeps the same thread for the whole transaction. In reactive code, a single logical request may execute across **many different threads** (operators can switch threads, `publishOn`/`subscribeOn`, event-loop hand-offs). A `ThreadLocal` would be lost the moment execution hops threads. So reactive transaction state is carried in the **Reactor Context** — an immutable key/value map that rides *with the subscription* through the operator chain, independent of which thread runs each step. ## @Transactional works with both You annotate methods the same way. Spring's `TransactionInterceptor` inspects the method's return type: if it returns a reactive type (`Mono`, `Flux`, any `Publisher`), it uses the reactive path and requires a `ReactiveTransactionManager` bean; otherwise it uses the imperative path with a `PlatformTransactionManager`. You cannot mix — a reactive method needs a reactive manager. ## When to use which - WebFlux + R2DBC / reactive Mongo → `ReactiveTransactionManager` (`R2dbcTransactionManager`). - Spring MVC + JDBC/JPA → `PlatformTransactionManager`. - Never wrap blocking JDBC in a reactive transaction expecting non-blocking behavior — JDBC blocks regardless. ## Gotcha There is no automatic bridging: a `@Transactional` method returning `void` but doing reactive work will be treated as *imperative* and the transaction will commit before the returned publisher runs. Always return the `Mono`/`Flux`.
- Which bean type must exist for a @Transactional method returning Mono to work?A ReactiveTransactionManager bean (e.g. R2dbcTransactionManager). If only a PlatformTransactionManager is present, Spring cannot manage the reactive method reactively.
- Why can't a reactive transaction use ThreadLocal like the blocking one does?Reactive execution hops between threads across operators/schedulers, so ThreadLocal state set on one thread is invisible on the next. State must ride with the subscription in the Reactor Context instead.
saying these in an interview costs you the question
- Claiming @Transactional automatically makes blocking JDBC non-blocking
- Saying reactive transaction state lives in ThreadLocal
- Thinking one manager type serves both imperative and reactive methods
- Returning void from a reactive @Transactional method