Complete empty Uni instances from the Mutiny reactive adapter

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>
This commit is contained in:
junhyeong9812
2026-09-09 14:47:10 +02:00
committed by Brian Clozel
parent 280861e7ee
commit d3d8e05fa9
2 changed files with 15 additions and 1 deletions
@@ -375,7 +375,7 @@ public class ReactiveAdapterRegistry {
void registerAdapters(ReactiveAdapterRegistry registry) {
ReactiveTypeDescriptor uniDesc = ReactiveTypeDescriptor.singleOptionalValue(
io.smallrye.mutiny.Uni.class,
() -> io.smallrye.mutiny.Uni.createFrom().nothing());
() -> io.smallrye.mutiny.Uni.createFrom().nullItem());
ReactiveTypeDescriptor multiDesc = ReactiveTypeDescriptor.multiValue(
io.smallrye.mutiny.Multi.class,
() -> io.smallrye.mutiny.Multi.createFrom().empty());
@@ -300,6 +300,20 @@ class ReactiveAdapterRegistryTests {
assertThat(((Mono<Integer>) target).block(FIVE_SECONDS)).isEqualTo(Integer.valueOf(1));
}
@Test
void fromNullValue() {
Object target = getAdapter(Uni.class).toPublisher(null);
assertThat(target).isInstanceOf(Mono.class);
assertThat(((Mono<Integer>) target).block(FIVE_SECONDS)).isNull();
}
@Test
void toUniFromEmptyPublisher() {
Object target = getAdapter(Uni.class).fromPublisher(Mono.empty());
assertThat(target).isInstanceOf(Uni.class);
assertThat(((Uni<Integer>) target).await().atMost(FIVE_SECONDS)).isNull();
}
@Test
void toMulti() {
List<Integer> sequence = Arrays.asList(1, 2, 3);