Skip to content

Explain that @Transactional doesn't cover asynchronous database work - #916

Merged
mkurz merged 1 commit into
playframework:mainfrom
mkurz:docs-transactional-async
Oct 9, 2026
Merged

mkurz merged 1 commit into
playframework:mainfrom
mkurz:docs-transactional-async

Conversation

@mkurz

@mkurz mkurz commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Purpose

@Transactional runs the action in DB.executeCall, so its transaction gets committed as soon as the action returns its result, not when that result completes. Ebean binds the transaction to the current thread, so:

  • database operations that run on another thread don't run in the transaction, even if they start before the action returns
  • the same goes for the action method, if an action composed between @Transactional and the method calls it asynchronously
  • if the action throws an exception, the transaction gets rolled back; if it returns a CompletionStage that fails instead, even one that failed already, the transaction gets committed

This documents that limitation in the Javadoc of @Transactional and in the "Transactional actions" section of the Ebean docs. It also shows how to run asynchronous database operations in a transaction: with DB.executeCall inside the asynchronous task, on a CustomExecutionContext for blocking database calls, together with the configuration of its dispatcher. The new snippets compile and run as part of the docs tests.

This documentation change leaves the behaviour of @Transactional unchanged.

Background Context

An action-wide asynchronous transaction, as asked for in #11, would need a substantially different design. Delaying the commit until the result completes wouldn't carry the transaction over to the threads that run the asynchronous work, and it would hold a pool connection while waiting on them, which can deadlock under load. Play deprecated its equivalent JPA annotation, @play.db.jpa.Transactional, in 2.7 and later removed it.

References

The transaction of a @transactional action gets committed as soon as
the action returns its result, and Ebean binds it to the current
thread. So database operations that run on another thread don't run
in it, and a CompletionStage that fails doesn't roll it back. Document
that, and how to run asynchronous database operations in a transaction
instead, with DB.executeCall on an execution context for blocking
database calls.
@mergify

mergify Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@mkurz
mkurz merged commit a47829b into playframework:main Oct 9, 2026
80 checks passed
@mkurz
mkurz deleted the docs-transactional-async branch October 9, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant