From 39106b58118aa95e0381b6bd1b001935dfe4a3c6 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 9 Jan 2026 10:25:44 +0000 Subject: [PATCH] Restore ApiVersionResolver contract Instead of making it async and having a sync subinterface variant, this restores ApiVersionResolver to be as it was with an async subinterface variant. ApiVersionStrategy, and the infrastructure invoking it, remains async first, but also accommodates sync resolvers. This should provide a better balance with backwards compatibility while also accommodating async version resolution as the less common scenario. See gh-36084 --- .../reactive/accept/ApiVersionResolver.java | 19 +++----------- ...lver.java => AsyncApiVersionResolver.java} | 25 ++++++------------- .../accept/DefaultApiVersionStrategy.java | 4 ++- .../accept/HeaderApiVersionResolver.java | 4 +-- .../MediaTypeParamApiVersionResolver.java | 4 +-- .../accept/PathApiVersionResolver.java | 4 +-- .../accept/QueryApiVersionResolver.java | 4 +-- .../server/RequestPredicatesTests.java | 3 +-- 8 files changed, 24 insertions(+), 43 deletions(-) rename spring-webflux/src/main/java/org/springframework/web/reactive/accept/{SyncApiVersionResolver.java => AsyncApiVersionResolver.java} (61%) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionResolver.java index 186abc062d7..7421679093c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/ApiVersionResolver.java @@ -17,7 +17,6 @@ package org.springframework.web.reactive.accept; import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Mono; import org.springframework.web.server.ServerWebExchange; @@ -25,7 +24,6 @@ import org.springframework.web.server.ServerWebExchange; * Contract to extract the version from a request. * * @author Rossen Stoyanchev - * @author Jonathan Kaplan * @since 7.0 */ @FunctionalInterface @@ -33,21 +31,12 @@ public interface ApiVersionResolver { /** * Resolve the version for the given exchange. - * @param exchange the current exchange - * @return {@code Mono} emitting the version value, or an empty {@code Mono} - * @since 7.0.3 - */ - Mono resolveApiVersion(ServerWebExchange exchange); - - /** - * Resolve the version for the given exchange. + *

Implementations of this method are expected to be non-blocking. + * If you need to resolve the version asynchronously, please implement the + * {@link AsyncApiVersionResolver} subinterface instead. * @param exchange the current exchange * @return the version value, or {@code null} if not found - * @deprecated in favor of {@link #resolveApiVersion(ServerWebExchange)} */ - @Deprecated(since = "7.0.3", forRemoval = true) - default @Nullable String resolveVersion(ServerWebExchange exchange) { - return null; - } + @Nullable String resolveVersion(ServerWebExchange exchange); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/SyncApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/AsyncApiVersionResolver.java similarity index 61% rename from spring-webflux/src/main/java/org/springframework/web/reactive/accept/SyncApiVersionResolver.java rename to spring-webflux/src/main/java/org/springframework/web/reactive/accept/AsyncApiVersionResolver.java index 9cabcbf1373..0a31f9e42d9 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/SyncApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/AsyncApiVersionResolver.java @@ -23,35 +23,26 @@ import org.springframework.web.server.ServerWebExchange; /** * Extension of {@link ApiVersionResolver} for implementations that resolve the - * version in an imperative way without blocking. + * version in an asynchronous way. * * @author Rossen Stoyanchev + * @author Jonathan Kaplan * @since 7.0.3 */ @FunctionalInterface -public interface SyncApiVersionResolver extends ApiVersionResolver { +public interface AsyncApiVersionResolver extends ApiVersionResolver { /** - * {@inheritDoc} - *

This method delegates to the synchronous - * {@link #resolveVersionValue} and wraps the result as {@code Mono}. - */ - @Override - default Mono resolveApiVersion(ServerWebExchange exchange) { - return Mono.justOrEmpty(resolveVersionValue(exchange)); - } - - /** - * Resolve the version for the given exchange imperatively without blocking. + * Resolve the version for the given exchange. * @param exchange the current exchange - * @return the version value, or {@code null} if not found + * @return {@code Mono} emitting the version value, or an empty {@code Mono} */ - @Nullable String resolveVersionValue(ServerWebExchange exchange); + Mono resolveVersionAsync(ServerWebExchange exchange); - @SuppressWarnings("removal") @Override default @Nullable String resolveVersion(ServerWebExchange exchange) { - return resolveVersionValue(exchange); + throw new UnsupportedOperationException( + "Async resolver does not support blocking resolution"); } } 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 a6fd704c2e5..4dfdb26a088 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,9 @@ public class DefaultApiVersionStrategy implements ApiVersionStrategy { @Override public Mono resolveApiVersion(ServerWebExchange exchange) { return Flux.fromIterable(this.versionResolvers) - .flatMap(resolver -> resolver.resolveApiVersion(exchange)) + .flatMap(resolver -> resolver instanceof AsyncApiVersionResolver asyncResolver ? + asyncResolver.resolveVersionAsync(exchange) : + Mono.justOrEmpty(resolver.resolveVersion(exchange))) .next(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderApiVersionResolver.java index 07863cefd91..4d7871dec63 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/HeaderApiVersionResolver.java @@ -26,7 +26,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 7.0 */ -public class HeaderApiVersionResolver implements SyncApiVersionResolver { +public class HeaderApiVersionResolver implements ApiVersionResolver { private final String headerName; @@ -37,7 +37,7 @@ public class HeaderApiVersionResolver implements SyncApiVersionResolver { @Override - public @Nullable String resolveVersionValue(ServerWebExchange exchange) { + public @Nullable String resolveVersion(ServerWebExchange exchange) { return exchange.getRequest().getHeaders().getFirst(this.headerName); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/MediaTypeParamApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/MediaTypeParamApiVersionResolver.java index db9b9acd3b5..c9967091088 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/MediaTypeParamApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/MediaTypeParamApiVersionResolver.java @@ -29,7 +29,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 7.0 */ -public class MediaTypeParamApiVersionResolver implements SyncApiVersionResolver { +public class MediaTypeParamApiVersionResolver implements ApiVersionResolver { private final MediaType compatibleMediaType; @@ -49,7 +49,7 @@ public class MediaTypeParamApiVersionResolver implements SyncApiVersionResolver @Override - public @Nullable String resolveVersionValue(ServerWebExchange exchange) { + public @Nullable String resolveVersion(ServerWebExchange exchange) { HttpHeaders headers = exchange.getRequest().getHeaders(); for (MediaType mediaType : headers.getAccept()) { if (this.compatibleMediaType.isCompatibleWith(mediaType)) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java index 06de39d20be..2da6819498b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/PathApiVersionResolver.java @@ -32,7 +32,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 7.0 */ -public class PathApiVersionResolver implements SyncApiVersionResolver { +public class PathApiVersionResolver implements ApiVersionResolver { private final int pathSegmentIndex; @@ -49,7 +49,7 @@ public class PathApiVersionResolver implements SyncApiVersionResolver { @Override - public String resolveVersionValue(ServerWebExchange exchange) { + public String resolveVersion(ServerWebExchange exchange) { int i = 0; for (PathContainer.Element e : exchange.getRequest().getPath().pathWithinApplication().elements()) { if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/QueryApiVersionResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/QueryApiVersionResolver.java index f81f832bfea..16606c75cbc 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/accept/QueryApiVersionResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/accept/QueryApiVersionResolver.java @@ -26,7 +26,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 7.0 */ -public class QueryApiVersionResolver implements SyncApiVersionResolver { +public class QueryApiVersionResolver implements ApiVersionResolver { private final String queryParamName; @@ -37,7 +37,7 @@ public class QueryApiVersionResolver implements SyncApiVersionResolver { @Override - public @Nullable String resolveVersionValue(ServerWebExchange exchange) { + public @Nullable String resolveVersion(ServerWebExchange exchange) { return exchange.getRequest().getQueryParams().getFirst(this.queryParamName); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java index 765a4e811e9..7a2fbc496ad 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.function.Function; import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -382,7 +381,7 @@ class RequestPredicatesTests { private static DefaultApiVersionStrategy apiVersionStrategy() { return new DefaultApiVersionStrategy( - List.of(exchange -> Mono.empty()), new SemanticApiVersionParser(), true, null, false, null, null); + List.of(exchange -> null), new SemanticApiVersionParser(), true, null, false, null, null); } }