Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring reactive transaction: subscription owns the context, not a thread

Last updated: 1 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

ReactiveTransactionManager carries transaction state in Reactor context, so work outside the subscribed chain is outside that transaction.

Build one chain

A reactive debit should insert its ledger row and update the account in one publisher chain, then apply a TransactionalOperator to that chain. The operator does not cover a second publisher subscribed manually inside doOnNext. That detached subscription can run after the first has failed or been cancelled. Subscription timing is part of the transaction contract.

Handle cancellation as a business case

A client disconnect can cancel a response publisher. If the transaction's completion depends on that publisher, cancellation can change when the database work is rolled back. Do not tie an essential payment or stock commitment to a response stream without deciding what cancellation means. A durable command record can separate acceptance from the client's connection; the debit lesson covers the normal rollback path.

Check the boundary under one subscription

Use a real R2DBC connection factory and subscribe once to the returned publisher. Inject failure after the first write; verify both writes are absent. Then cancel before completion and check database state. This is an architecture sketch; cancellation behavior must be verified against the chosen driver and transaction manager.

Implementation sketch

Java
Mono<Void> transfer = balances.debit(accountId, 47)
    .then(ledger.record(accountId, 47))
    .then();
return transactionOperator.transactional(transfer);

Cost and verification

One reactive transaction occupies a database connection until terminal completion. Slow upstream work and never-ending publishers can keep that connection unavailable to the pool.

Common Mistakes

  • Do not call subscribe inside a transactional chain for a required write.
  • Do not use a thread-local transaction assumption in Reactor code.
  • Do not ignore cancellation when a client disconnects before completion.

Read next

Spring R2DBC reactive transaction: roll back the event marker with a rejected debit, Spring R2DBC subscription boundary: constructing SQL does not execute it, Spring R2DBC fixture isolation: keep incompatible H2 versions out of one test classpath, Spring imperative transaction: a worker thread does not inherit it.

spring
spring-boot
data-transactions
reactive-transaction-context-ownership
Storage details