From d3d8e05fa9d4fefa3961154eaaa210268750ec68 Mon Sep 17 00:00:00 2001 From: junhyeong9812 Date: Wed, 9 Sep 2026 14:26:54 +0900 Subject: [PATCH] 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 --- .../core/ReactiveAdapterRegistry.java | 2 +- .../core/ReactiveAdapterRegistryTests.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java index 5be957ecc2b8..e629fb3b64ed 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -375,7 +375,7 @@ private static class MutinyRegistrar { 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()); diff --git a/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java b/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java index 355e165f231c..0d5129edaf15 100644 --- a/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java +++ b/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java @@ -300,6 +300,20 @@ void fromUni() { assertThat(((Mono) 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) target).block(FIVE_SECONDS)).isNull(); + } + + @Test + void toUniFromEmptyPublisher() { + Object target = getAdapter(Uni.class).fromPublisher(Mono.empty()); + assertThat(target).isInstanceOf(Uni.class); + assertThat(((Uni) target).await().atMost(FIVE_SECONDS)).isNull(); + } + @Test void toMulti() { List sequence = Arrays.asList(1, 2, 3);