Note: AI-assisted wording/structure; problem statement, proposal are my own.
Summary
I’d like to discuss improving null-safety ergonomics for programmatic transactions in spring-tx.
TransactionOperations#execute(TransactionCallback<T>) is correctly @Nullable by contract, since callbacks may return null. In @NullMarked / JSpecify codebases, this propagates nullable types into many call sites, including flows where null is not a valid business outcome.
Context / real-world problem
While refactoring production service code to @NullMarked, we repeatedly ended up with patterns like:
var result = Objects.requireNonNull(
transactionTemplate.execute(status -> repository.merge(entity)),
"Transactional callback returned null; this should never happen"
);
This is valid, but repetitive. We introduced an internal wrapper (TransactionalExecutor) + dedicated non-null callback type to keep call sites explicit and reduce repeated null guards.
Proposal direction
I’d like to discuss whether Spring should offer an official non-null path for this common case.
One possible shape:
<T> T execute(NonNullTransactionCallback<T> action) throws TransactionException;
Another possible shape is a distinct method name (for example executeRequired(...)) that enforces non-null results.
Naming in this issue (execute, executeRequired, NonNullTransactionCallback) is illustrative only and fully open for discussion.
Important API caveat (with minimal repro)
If a non-null callback variant is added as an overload next to existing execute(TransactionCallback<T>), lambda calls can become ambiguous because both callback types are SAM interfaces with the same shape.
Minimal example:
interface Test {
void test(TestCallback cb);
void test(TestCallback2 cb);
}
interface TestCallback { void test(); }
interface TestCallback2 { void test(); }
new Implementation().test(() -> System.out.println("Test")); // ambiguous
Then callers must cast:
new Implementation().test((TestCallback) () -> System.out.println("Test"));
I’m not sure this would be acceptable ergonomically for Spring users, so I wanted to raise this explicitly in the design discussion.
Why framework-level support could still help
- Common framework-driven pain point in strict nullness code.
- Reduces repetitive
requireNonNull(...) boilerplate.
- Makes intent explicit at call sites.
- Encourages a consistent idiom across projects.
Questions for maintainers
- Is a first-class non-null transaction execution path aligned with Spring transaction API design?
- Given lambda ambiguity risk, what API shape would you prefer?
- overload with dedicated callback type,
- distinct method name,
- or another approach.
- If runtime enforcement is used, what exception semantics/message would be preferred when
null is returned unexpectedly?
If this direction is useful, I’m happy to prepare a PR.
Note: AI-assisted wording/structure; problem statement, proposal are my own.
Summary
I’d like to discuss improving null-safety ergonomics for programmatic transactions in
spring-tx.TransactionOperations#execute(TransactionCallback<T>)is correctly@Nullableby contract, since callbacks may returnnull. In@NullMarked/ JSpecify codebases, this propagates nullable types into many call sites, including flows wherenullis not a valid business outcome.Context / real-world problem
While refactoring production service code to
@NullMarked, we repeatedly ended up with patterns like:This is valid, but repetitive. We introduced an internal wrapper (
TransactionalExecutor) + dedicated non-null callback type to keep call sites explicit and reduce repeated null guards.Proposal direction
I’d like to discuss whether Spring should offer an official non-null path for this common case.
One possible shape:
Another possible shape is a distinct method name (for example
executeRequired(...)) that enforces non-null results.Naming in this issue (
execute,executeRequired,NonNullTransactionCallback) is illustrative only and fully open for discussion.Important API caveat (with minimal repro)
If a non-null callback variant is added as an overload next to existing
execute(TransactionCallback<T>), lambda calls can become ambiguous because both callback types are SAM interfaces with the same shape.Minimal example:
Then callers must cast:
I’m not sure this would be acceptable ergonomically for Spring users, so I wanted to raise this explicitly in the design discussion.
Why framework-level support could still help
requireNonNull(...)boilerplate.Questions for maintainers
nullis returned unexpectedly?If this direction is useful, I’m happy to prepare a PR.