PlatformTransactionManager binds its transaction to the current thread; Executor tasks need their own explicit boundary.
Spring imperative transaction: a worker thread does not inherit it
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
@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
