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

Spring imperative transaction: a worker thread does not inherit it

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

PlatformTransactionManager binds its transaction to the current thread; Executor tasks need their own explicit boundary.

The caller's annotation ends at the thread

A controller invokes a transactional IntakeService, then submits a Runnable to an Executor. The task can start before the caller commits, and it does not inherit the caller's transaction. If the task queries the intake row, it may see no row or an old row depending on isolation. If it writes, that write may commit even after the caller rolls back. Copying a SecurityContext or trace ID does not transfer a database transaction.

Move work across a durable boundary

For required follow-up work, insert a job or outbox record in the caller's transaction and let a separate worker claim it after commit. For disposable parallel computation, pass immutable input and collect results before committing the caller's decision. When the task itself writes to a database, give that worker its own service transaction. Async delivery and transaction events cover different failure windows.

Verify ordering and rollback

A useful test blocks the worker, rolls the caller back, then releases the worker. The worker must not write a success marker for a reservation that never committed. The sketch names the boundary; it does not establish queue durability or retry semantics.

Implementation sketch

Java
@Transactional
public void accept(IntakeCommand command) {
    intakes.insert(command.id(), command.units());
    pendingJobs.insert(command.id(), "RECOUNT");
}

@Transactional
public void processPending(String intakeId) {
    Intake intake = intakes.get(intakeId);
    counts.recount(intake);
}

Cost and verification

A durable job row adds one write and later polling or notification. Passing an imperative transaction into an Executor is not a supported way to save that cost.

Common Mistakes

  • Do not expect @Transactional to follow CompletableFuture or @Async onto another thread.
  • Do not treat afterCommit publication as durable delivery by itself.
  • Do not let a worker act on state that the caller may still roll back.

Read next

Spring task executors: capacity, rejection and lost context, Spring async work versus durable delivery: separate latency from recovery, Spring outbox transaction boundaries: where atomicity ends, Spring REQUIRES_NEW: budget the second database connection.

Related boundary

Spring Integration DirectChannel versus ExecutorChannel: where the transaction stops

spring
spring-boot
data-transactions
transaction-thread-boundary
Storage details