Jakarta EE

jOOQ Transaction API: Why NESTED Beats REQUIRED by Default

Most Java developers have met transactional logic through Spring's @Transactional annotation. JOOQ takes a different path: an explicit transaction() lambda that defaults to NESTED propagation rather than Spring's REQUIRED.

The practical difference matters more than the philosophical one. REQUIRED participates in an outer transaction if one exists; NESTED creates a real SAVEPOINT. When the inner unit fails, REQUIRED can leave the outer transaction in a half-applied state. NESTED rolls back only the inner work, preserving the outer.

JOOQ 3.4 shipped the JDBC API; jOOQ 3.17 adds the same for R2DBC. Standalone statements auto-commit by default. Normal lambda completion commits; exceptions roll back. Recoverable exceptions caught inside the lambda do not trigger rollback.

The lambda-first mental model

The jOOQ approach wraps the whole unit of work inside a transaction() lambda. If the lambda returns normally, the work commits. If it throws, the work rolls back. The mental model matches Jakarta EE and Spring's annotation-driven behaviour, but without the implicit magic that trips up teams debugging partial commits.

Your control flow stays yours. If you catch a recoverable exception inside the lambda—like a duplicate key constraint—jOOQ keeps the transaction alive. You can continue the unit of work without losing your progress.

NESTED vs REQUIRED: A Direct Comparison

Aspect REQUIRED NESTED
Outer transaction exists Participates Creates SAVEPOINT
Inner failure Outer may be half-committed Only inner rolls back
SAVEPOINT required No Yes
Works on all RDBMS Yes No
Default in Spring Yes No
Default in jOOQ No Yes

Our analysis

REQUIRED is convenient for the happy path and dangerous on the unhappy path. NESTED is the opposite. The real problem is that REQUIRED's default masks a design smell: when every service method is blindly annotated, you stop thinking about what should be atomic. NESTED forces that thinking because partial rollbacks only work when you understand your boundaries.

Who wins:** Teams on PostgreSQL, MySQL, or Oracle running business logic that spans multiple tables. Nested operations like "try this, then that" work as intended without manual savepoint management.

Who loses:** Teams on databases without SAVEPOINT support. SQL Server and certain embedded databases fall here. For them, REQUIRED is not just the default—it is the only safe choice.

What to watch:** The R2DBC adoption curve. Reactive stacks historically lacked clean transaction options; jOOQ 3.17 changes that. If your team is evaluating reactive Java, this API matters more than the propagation debate.

Migrating a Spring service to jOOQ

Audit every existing @Transactional annotation before replacing anything. If a method relies on participating in an outer transaction, explicitly switch to REQUIRED. For independent units of work, NESTED is cleaner and avoids the half-committed state. Test against your actual database to confirm SAVEPOINT support before changing the default.

Bottom line

JOOQ's lambda API is more explicit than Spring's annotations, and NESTED is a smarter default when your database supports it. Adopt both deliberately, and your future debugging sessions will be shorter.

Key takeaways

  • NESTED propagation avoids the half-committed state that REQUIRED can produce.
  • SAVEPOINT support is the hard requirement for switching the default.
  • The lambda-based API gives you control flow without losing transactional safety.
  • R2DBC support in jOOQ 3.17 brings the same clarity to reactive stacks.

Frequently asked questions

When did jOOQ add its transaction API?

JOOQ 3.4 introduced the API for JDBC, and jOOQ 3.17 added the equivalent for R2DBC.

Why is NESTED better than REQUIRED?

NESTED wraps each unit of work in a real SAVEPOINT, so a failure rolls back only the inner work without breaking the outer transaction. REQUIRED can leave the outer transaction in a half-applied state when the inner method fails.

Does NESTED work with every database?

No. NESTED requires SAVEPOINT support, which is not available in every RDBMS that supports transactions.

What happens if my code catches an exception inside the lambda?

The transaction stays open. JOOQ only rolls back when the lambda exits with an unhandled exception, which lets you handle recoverable errors—like duplicate key violations—without losing your work.

Is NESTED the default in Spring?

No. Spring defaults to REQUIRED, as do Jakarta EE specifications. JOOQ deliberately chose NESTED as its default for the reasons outlined above.

Related