Skip to content

Complete empty Uni instances from the Mutiny reactive adapter - #37259

Merged
bclozel merged 1 commit into
spring-projects:7.0.xfrom
junhyeong9812:fix/mutiny-uni-empty-value
Sep 9, 2026
Merged

Complete empty Uni instances from the Mutiny reactive adapter#37259
bclozel merged 1 commit into
spring-projects:7.0.xfrom
junhyeong9812:fix/mutiny-uni-empty-value

Conversation

@junhyeong9812

Copy link
Copy Markdown
Contributor

Overview

The Mutiny Uni adapter in ReactiveAdapterRegistry supplies Uni.createFrom().nothing() as its empty value. That Uni never signals, so everywhere the framework adapts a null source - most visibly a WebFlux handler method declared to return Uni<T> that returns null - the resulting Publisher never completes and the request hangs, while the same handler declared with Mono<T> completes empty. This PR changes the supplier to Uni.createFrom().nullItem().

Problem

ReactiveAdapter.toPublisher(Object) substitutes getDescriptor().getEmptyValue() when the source is null. The Uni descriptor is registered with ReactiveTypeDescriptor.singleOptionalValue, which declares that the type supports an empty value, but the supplied empty value is Uni.createFrom().nothing() - a Uni that never emits an item, a failure, or completion. Adapting it produces a Publisher that never signals, so a blocking or subscribing consumer waits until its own timeout.

All sibling registrations supply an empty value that completes immediately: Mono::empty, Maybe::empty, Completable::complete, CompletableDeferred(null), and the Multi registration itself uses Multi.createFrom().empty(). The Uni registration is also asymmetric with its own fromPublisher function: adapting an empty Publisher produces a Uni that completes with a null item, but the registered empty value cannot make the trip back.

Fix

The empty-value supplier now uses Uni.createFrom().nullItem(). Mutiny converts a Uni with a null item to a Publisher that completes without emitting an item (Reactive Streams forbids onNext(null)), which matches the sibling adapters and makes the round trip through fromPublisher symmetric. The descriptor instance is shared by the Mutiny 1 and Mutiny 2 registrations, so both are covered.

Two tests are added: adapting a null source completes empty within a timeout (previously it hung), and adapting an empty Publisher to Uni yields a null item, pinning the round-trip contract. The full spring-core test suite passes.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 9, 2026
@bclozel bclozel added the in: core Issues in core modules (aop, beans, core, context, expression) label Sep 9, 2026
@bclozel bclozel self-assigned this Sep 9, 2026
@bclozel
bclozel changed the base branch from main to 7.0.x September 9, 2026 12:45
@bclozel bclozel added the type: bug A general bug label Sep 9, 2026
@bclozel bclozel added this to the 7.0.10 milestone Sep 9, 2026
@bclozel bclozel removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 9, 2026
The Mutiny Uni adapter registers its empty-value supplier as
Uni.createFrom().nothing(), which returns a Uni that never signals an
item, a failure, or completion. Every sibling registration supplies an
empty value that completes immediately: Mono.empty(), Maybe.empty(),
Completable.complete(), and CompletableDeferred(null); the Multi
registration uses Multi.createFrom().empty() as well.

ReactiveAdapter.toPublisher(null) substitutes that empty value whenever
a null source needs to be adapted, for example when a WebFlux handler
method with a Uni return type returns null. With a never-completing
empty value the resulting Publisher emits no signal at all, so the
response is never written and the request hangs until a timeout,
whereas the same handler declared with Mono completes empty. The
adapter also becomes asymmetric with its own fromPublisher function,
which adapts an empty Publisher to a Uni that completes with a null
item.

The supplier now uses Uni.createFrom().nullItem(), whose conversion to
a Publisher completes without emitting an item, matching the sibling
adapters and the round-trip through fromPublisher. The descriptor is
shared by the Mutiny 1 and Mutiny 2 registrations, so both paths are
covered.

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
@bclozel
bclozel force-pushed the fix/mutiny-uni-empty-value branch from c572158 to d3d8e05 Compare September 9, 2026 12:47
@bclozel
bclozel merged commit d3d8e05 into spring-projects:7.0.x Sep 9, 2026
2 checks passed
@bclozel

bclozel commented Sep 9, 2026

Copy link
Copy Markdown
Member

Thanks @junhyeong9812 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants