From 74a4b1a694f78bc1a04540c748cabe73c239b099 Mon Sep 17 00:00:00 2001 From: Gimin Kim <138752849+Gimini-3@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:35:55 +0900 Subject: [PATCH] Preserve async API version resolver order Prior to this commit, the `DefaultApiVersionStrategy` reactive variant would attempt to resolve the API version with the first resolver that replies with a version. This contradicts the API that registers resolvers in order. This commit ensures that each resolver is called in order. Signed-off-by: Gimin Kim <138752849+Gimini-3@users.noreply.github.com> --- .../accept/DefaultApiVersionStrategy.java | 2 +- .../DefaultApiVersionStrategiesTests.java | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategy.java index 4dfdb26a088..f2adc8cd892 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategy.java @@ -169,7 +169,7 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy { @Override public Mono resolveApiVersion(ServerWebExchange exchange) { return Flux.fromIterable(this.versionResolvers) - .flatMap(resolver -> resolver instanceof AsyncApiVersionResolver asyncResolver ? + .concatMap(resolver -> resolver instanceof AsyncApiVersionResolver asyncResolver ? asyncResolver.resolveVersionAsync(exchange) : Mono.justOrEmpty(resolver.resolveVersion(exchange))) .next(); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategiesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategiesTests.java index f8b2c7c8889..0906989e7e9 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategiesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/accept/DefaultApiVersionStrategiesTests.java @@ -23,6 +23,7 @@ import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import reactor.test.publisher.TestPublisher; import org.springframework.web.accept.InvalidApiVersionException; import org.springframework.web.accept.MissingApiVersionException; @@ -128,10 +129,42 @@ class DefaultApiVersionStrategiesTests { .withMessage("versionRequired cannot be set to true if a defaultVersion is also configured"); } + @Test + void resolveVersionWithResolversInOrder() { + TestPublisher firstResolverPublisher = TestPublisher.create(); + ApiVersionStrategy strategy = apiVersionStrategy(List.of( + (AsyncApiVersionResolver) exchange -> firstResolverPublisher.flux().next(), + (AsyncApiVersionResolver) exchange -> Mono.just("2.0"))); + + StepVerifier.create(strategy.resolveApiVersion( + MockServerWebExchange.from(MockServerHttpRequest.get("/")))) + .then(() -> firstResolverPublisher.emit("1.0")) + .expectNext("1.0") + .verifyComplete(); + } + + @Test + void resolveVersionPropagatesErrorFromEarlierResolver() { + TestPublisher firstResolverPublisher = TestPublisher.create(); + ApiVersionStrategy strategy = apiVersionStrategy(List.of( + (AsyncApiVersionResolver) exchange -> firstResolverPublisher.flux().next(), + (AsyncApiVersionResolver) exchange -> Mono.just("2.0"))); + + StepVerifier.create(strategy.resolveApiVersion( + MockServerWebExchange.from(MockServerHttpRequest.get("/")))) + .then(() -> firstResolverPublisher.error(new IllegalStateException("test"))) + .expectErrorMessage("test") + .verify(); + } + private static DefaultApiVersionStrategy apiVersionStrategy() { return apiVersionStrategy(null, false, null); } + private static DefaultApiVersionStrategy apiVersionStrategy(List resolvers) { + return new DefaultApiVersionStrategy(resolvers, parser, null, null, false, null, null); + } + private static DefaultApiVersionStrategy apiVersionStrategy( @Nullable String defaultVersion, boolean detectSupportedVersions, @Nullable Predicate> supportedVersionPredicate) {