From cce27716390ed075d907af02e4a3c23d58e6fecd Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 9 May 2025 16:37:23 +0100 Subject: [PATCH] Add API versioning reference documentation See gh-34569 --- framework-docs/modules/ROOT/nav.adoc | 3 + .../ROOT/pages/web/webflux-versioning.adoc | 80 ++++++++++++++++++ .../ROOT/pages/web/webflux/config.adoc | 57 +++++++++++++ .../controller/ann-requestmapping.adoc | 79 ++++++++++++++++++ .../ROOT/pages/web/webmvc-versioning.adoc | 81 +++++++++++++++++++ .../web/webmvc/mvc-config/api-version.adoc | 26 ++++++ .../mvc-controller/ann-requestmapping.adoc | 80 ++++++++++++++++++ .../mvcconfigapiversion/WebConfiguration.java | 32 ++++++++ .../mvcconfigapiversion/WebConfiguration.kt | 31 +++++++ 9 files changed, 469 insertions(+) create mode 100644 framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc create mode 100644 framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc create mode 100644 framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/api-version.adoc create mode 100644 framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.java create mode 100644 framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.kt diff --git a/framework-docs/modules/ROOT/nav.adoc b/framework-docs/modules/ROOT/nav.adoc index 7df6fde4d12..f8389c3fdcf 100644 --- a/framework-docs/modules/ROOT/nav.adoc +++ b/framework-docs/modules/ROOT/nav.adoc @@ -197,6 +197,7 @@ *** xref:web/webmvc/mvc-uri-building.adoc[] *** xref:web/webmvc/mvc-ann-async.adoc[] *** xref:web/webmvc-cors.adoc[] +*** xref:web/webmvc-versioning.adoc[] *** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[] *** xref:web/webmvc/mvc-security.adoc[] *** xref:web/webmvc/mvc-caching.adoc[] @@ -225,6 +226,7 @@ **** xref:web/webmvc/mvc-config/static-resources.adoc[] **** xref:web/webmvc/mvc-config/default-servlet-handler.adoc[] **** xref:web/webmvc/mvc-config/path-matching.adoc[] +**** xref:web/webmvc/mvc-config/api-version.adoc[] **** xref:web/webmvc/mvc-config/advanced-java.adoc[] **** xref:web/webmvc/mvc-config/advanced-xml.adoc[] *** xref:web/webmvc/mvc-http2.adoc[] @@ -292,6 +294,7 @@ *** xref:web/webflux-functional.adoc[] *** xref:web/webflux/uri-building.adoc[] *** xref:web/webflux-cors.adoc[] +*** xref:web/webflux-versioning.adoc[] *** xref:web/webflux/ann-rest-exceptions.adoc[] *** xref:web/webflux/security.adoc[] *** xref:web/webflux/caching.adoc[] diff --git a/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc b/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc new file mode 100644 index 00000000000..62ff351ba5b --- /dev/null +++ b/framework-docs/modules/ROOT/pages/web/webflux-versioning.adoc @@ -0,0 +1,80 @@ +[[webflux-versioning]] += API Versioning +:page-section-summary-toc: 1 + +[.small]#xref:web/webmvc-versioning.adoc[See equivalent in the Servlet stack]# + +Spring WebFlux supports API versioning. This section provides an overview of the support +and underlying strategies. + +Please, see also related content in: + +- Use xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-version[API Versions] +to map requests to annotated controller methods +- Configure API versioning in xref:web/webflux/config.adoc#webflux-config-api-version[WebFlux Config] + + + +[[webflux-versioning-strategy]] +== ApiVersionStrategy +[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-strategy[See equivalent in the Servlet stack]# + +This strategy holds all application preferences about how to manage versioning. +It delegates to xref:#webflux-versioning-resolver[ApiVersionResolver] to resolve versions +from requests, and to xref:#webflux-versioning-parser[ApiVersionParser] to parse raw version +values into `Comparable`. It also helps to xref:#webflux-versioning-validation[validate] +request versions. + +NOTE: `ApiVersionStrategy` helps to map requests to `@RequestMapping` controller methods, +and is initialized by the WebFlux config. Typically, applications do not interact directly with it. + + + + +[[webflux-versioning-resolver]] +== ApiVersionResolver +[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-resolver[See equivalent in the Servlet stack]# + +This strategy resolves the API version from a request. The WebFlux config provides built-in +options to resolve from a header, a request parameter, or from the URL path. +You can also use a custom `ApiVersionResolver`. + + + + +[[webflux-versioning-parser]] +== ApiVersionParser +[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-parser[See equivalent in the Servlet stack]# + +This strategy helps to parse raw version values into `Comparable`, which helps to +compare, sort, and select versions. By default, the built-in `SemanticApiVersionParser` +parses a version into `major`, `minor`, and `patch` integer values. Minor and patch +values are set to 0 if not present. + + + + +[[webflux-versioning-validation]] +== Validation +[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-validation[See equivalent in the Servlet stack]# + +If a request version is not supported, `InvalidApiVersionException` is raised resulting +in a 400 response. By default, the list of supported versions is initialized from declared +versions in annotated controller mappings. You can add to that list, or set it explicitly +to a fixed set of versions (i.e. ignoring declared ones) through the MVC config. + +By default, a version is required when API versioning is enabled, but you can turn that +off in which case the highest available version is used. You can also specify a default +version. `MissingApiVersionException` is raised resulting in a 400 response when a +version is required but not present. + + + + +[[webflux-versioning-mapping]] +== Request Mapping +[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-mapping[See equivalent in the Servlet stack]# + +`ApiVersionStrategy` supports the mapping of requests to annotated controller methods. +See xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-version[API Versions] +for more details. \ No newline at end of file diff --git a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc index 8d1c2e7eac0..f2a75ba8019 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc @@ -686,6 +686,63 @@ reliance on it. +[[webflux-config-api-version]] +== API Version +[.small]#xref:web/webmvc/mvc-config/api-version.adoc[See equivalent in the Servlet stack]# + +To enable API versioning with a request header, use the following: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim"] +---- + @Configuration + public class WebConfiguration implements WebFluxConfigurer { + + @Override + public void configureApiVersioning(ApiVersionConfigurer configurer) { + configurer.useRequestHeader("X-API-Version"); + } + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim"] +---- + @Configuration + class WebConfiguration : WebMvcConfigurer { + + override fun configureApiVersioning(configurer: ApiVersionConfigurer) { + configurer.useRequestHeader("X-API-Version") + } + } +---- +====== + +Alternatively, the version can be resolved from a request parameter, from a path segment, +or through a custom `ApiVersionResolver`. + +TIP: When resolving from a path segment, consider configuring a path prefix once in +xref:web/webmvc/mvc-config/path-matching.adoc[Path Matching] options. + +Raw version values are parsed with `SemanticVersionParser` by default, but you can use +a custom xref:web/webflux-versioning.adoc#webflux-versioning-parser[ApiVersionParser]. + +"Supported" versions are transparently detected from versions declared in request mappings +for convenience, but you can also set the list of supported versions explicitly, and +ignore declared ones. Requests with a version that is not supported are rejected with an +`InvalidApiVersionException` resulting in a 400 response. + +Once API versioning is configured, you can begin to map requests to +xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-version[controller methods] +according to the request version. + + + + [[webflux-config-blocking-execution]] == Blocking Execution diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc index ba618c0c8d8..295a16a264e 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc @@ -408,6 +408,85 @@ Kotlin:: ====== +[[webflux-ann-requestmapping-version]] +== API Version +[.small]#xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-version[See equivalent in the Servlet stack]# + +There is no standard way to specify an API version, so you need to configure that first +through the xref:web/webflux/config.adoc#webflux-config-api-version[WebFlux Config] along with other +config options. This results in the creation of an +xref:web/webflux-versioning.adoc#webflux-versioning-strategy[ApiVersionStrategy] that in +supports request mapping. + +Once API versioning is enabled, you can begin to map requests with versions. +The `@RequestMapping` version attribute supports the following: + +- No value -- match any version +- Fixed version ("1.2") -- match the given version only +- Baseline version ("1.2+") -- match the given version and above + +If multiple controller methods have a version less than or equal to the request version, +the one closest to the request version is considered for mapping purposes, +in effect superseding the rest. + +To illustrate this, consider the following controller mappings: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes"] +---- + @RestController + @RequestMapping("/account/{id}") + public class AccountController { + + @GetMapping // <1> + public Account getAccount() { + } + + @GetMapping(version = "1.1") // <2> + public Account getAccount1_1() { + } + + @GetMapping(version = "1.2+") // <3> + public Account getAccount1_2() { + } + + @GetMapping(version = "1.5") // <4> + public Account getAccount1_5() { + } + } +---- +<1> match any version +<2> match version 1.1 +<3> match version 1.2 and above +<4> match version 1.5 +====== + +For request with version `"1.3"`: + +- (1) matches as it matches any version +- (2) does not match +- (3) matches as it matches 1.2 and above, and is *chosen* as the highest match +- (4) is higher and does not match + +For request with version `"1.5"`: + +- (1) matches as it matches any version +- (2) does not match +- (3) matches as it matches 1.2 and above +- (4) matches and is *chosen* as the highest match + +A request with version `"1.6"` does not have a match. (1) and (3) do match, but are +superseded by (4), which does not match. In this scenario, `NotAcceptableApiVersionException` +is raised resulting in a 400 response. + +NOTE: The above assumes the request version is a "supported" versions. If not it would +fail xref:web/webflux-versioning.adoc#webflux-versioning-validation[Validation]. + + + [[webflux-ann-requestmapping-head-options]] == HTTP HEAD, OPTIONS diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc new file mode 100644 index 00000000000..2546626fd50 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/web/webmvc-versioning.adoc @@ -0,0 +1,81 @@ +[[mvc-versioning]] += API Versioning +:page-section-summary-toc: 1 + +[.small]#xref:web/webflux-versioning.adoc[See equivalent in the Reactive stack]# + +Spring MVC supports API versioning. This section provides an overview of the support +and underlying strategies. + +Please, see also related content in: + +- Use xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-version[API Version] +to map requests to annotated controller methods +- Configure API versioning in xref:web/webmvc/mvc-config/api-version.adoc[MVC Config] + + + + +[[mvc-versioning-strategy]] +== ApiVersionStrategy +[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-strategy[See equivalent in the Reactive stack]# + +This strategy holds all application preferences about how to manage versioning. +It delegates to xref:#mvc-versioning-resolver[ApiVersionResolver] to resolve versions +from requests, and to xref:#mvc-versioning-parser[ApiVersionParser] to parse raw version +values into `Comparable`. It also helps to xref:#mvc-versioning-validation[validate] +request versions. + +NOTE: `ApiVersionStrategy` helps to map requests to `@RequestMapping` controller methods, +and is initialized by the MVC config. Typically, applications do not interact directly with it. + + + + +[[mvc-versioning-resolver]] +== ApiVersionResolver +[.small]#xref:web/webmvc-versioning.adoc#mvc-versioning-resolver[See equivalent in the Reactive stack]# + +This strategy resolves the API version from a request. The MVC config provides built-in +options to resolve from a header, from a request parameter, or from the URL path. +You can also use a custom `ApiVersionResolver`. + + + + +[[mvc-versioning-parser]] +== ApiVersionParser +[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-parser[See equivalent in the Reactive stack]# + +This strategy helps to parse raw version values into `Comparable`, which helps to +compare, sort, and select versions. By default, the built-in `SemanticApiVersionParser` +parses a version into `major`, `minor`, and `patch` integer values. Minor and patch +values are set to 0 if not present. + + + + +[[mvc-versioning-validation]] +== Validation +[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-validation[See equivalent in the Reactive stack]# + +If a request version is not supported, `InvalidApiVersionException` is raised resulting +in a 400 response. By default, the list of supported versions is initialized from declared +versions in annotated controller mappings. You can add to that list, or set it explicitly +to a fixed set of versions (i.e. ignoring declared ones) through the MVC config. + +By default, a version is required when API versioning is enabled, but you can turn that +off in which case the highest available version is used. You can also specify a default +version. `MissingApiVersionException` is raised resulting in a 400 response when a +version is required but not present. + + + + +[[mvc-versioning-mapping]] +== Request Mapping +[.small]#xref:web/webflux-versioning.adoc#webflux-versioning-mapping[See equivalent in the Reactive stack]# + +`ApiVersionStrategy` supports the mapping of requests to annotated controller methods. +See xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-version[API Version] +for more details. \ No newline at end of file diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/api-version.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/api-version.adoc new file mode 100644 index 00000000000..88ffa09a6a1 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/api-version.adoc @@ -0,0 +1,26 @@ +[[mvc-config-api-version]] += API Version + +[.small]#xref:web/webflux/config.adoc#webflux-config-api-version[See equivalent in the Reactive stack]# + +To enable API versioning with a request header, use the following: + +include-code::./WebConfiguration[tag=snippet,indent=0] + +Alternatively, the version can be resolved from a request parameter, from a path segment, +or through a custom `ApiVersionResolver`. + +TIP: When resolving from a path segment, consider configuring a path prefix once in +xref:web/webmvc/mvc-config/path-matching.adoc[Path Matching] options. + +Raw version values are parsed with `SemanticVersionParser` by default, but you can use +a custom xref:web/webmvc-versioning.adoc#mvc-versioning-parser[ApiVersionParser]. + +"Supported" versions are transparently detected from versions declared in request mappings +for convenience, but you can also set the list of supported versions explicitly, and +ignore declared ones. Requests with a version that is not supported are rejected with an +`InvalidApiVersionException` resulting in a 400 response. + +Once API versioning is configured, you can begin to map requests to +xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-version[controller methods] +according to the request version. \ No newline at end of file diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc index 5ed0bfa3810..cfd4cbb2814 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc @@ -429,6 +429,86 @@ xref:web/webmvc/mvc-controller/ann-requestmapping.adoc#mvc-ann-requestmapping-co instead. +[[mvc-ann-requestmapping-version]] +== API Version +[.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-version[See equivalent in the Reactive stack]# + +There is no standard way to specify an API version, so you need to configure that first +through the xref:web/webmvc/mvc-config/api-version.adoc[MVC Config] along with other +config options. This results in the creation of an +xref:web/webmvc-versioning.adoc#mvc-versioning-strategy[ApiVersionStrategy] that in turn +supports request mapping. + +Once API versioning is enabled, you can begin to map requests with versions. +The `@RequestMapping` version attribute supports the following: + +- No value -- match any version +- Fixed version ("1.2") -- match the given version only +- Baseline version ("1.2+") -- match the given version and above + +If multiple controller methods have a version less than or equal to the request version, +the one closest to the request version is considered for mapping purposes, +in effect superseding the rest. + +To illustrate this, consider the following controller mappings: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes"] +---- + @RestController + @RequestMapping("/account/{id}") + public class AccountController { + + @GetMapping // <1> + public Account getAccount() { + } + + @GetMapping(version = "1.1") // <2> + public Account getAccount1_1() { + } + + @GetMapping(version = "1.2+") // <3> + public Account getAccount1_2() { + } + + @GetMapping(version = "1.5") // <4> + public Account getAccount1_5() { + } + } +---- +<1> match any version +<2> match version 1.1 +<3> match version 1.2 and above +<4> match version 1.5 +====== + +For request with version `"1.3"`: + +- (1) matches as it matches any version +- (2) does not match +- (3) matches as it matches 1.2 and above, and is *chosen* as the highest match +- (4) is higher and does not match + +For request with version `"1.5"`: + +- (1) matches as it matches any version +- (2) does not match +- (3) matches as it matches 1.2 and above +- (4) matches and is *chosen* as the highest match + +A request with version `"1.6"` does not have a match. (1) and (3) do match, but are +superseded by (4), which does not match. In this scenario, `NotAcceptableApiVersionException` +is raised resulting in a 400 response. + +NOTE: The above assumes the request version is a "supported" versions. If not it would +fail xref:web/webmvc-versioning.adoc#mvc-versioning-validation[Validation]. + + + + [[mvc-ann-requestmapping-head-options]] == HTTP HEAD, OPTIONS [.small]#xref:web/webflux/controller/ann-requestmapping.adoc#webflux-ann-requestmapping-head-options[See equivalent in the Reactive stack]# diff --git a/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.java b/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.java new file mode 100644 index 00000000000..a69e95e856a --- /dev/null +++ b/framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.web.webmvc.mvcconfig.mvcconfigapiversion; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ApiVersionConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +// tag::snippet[] +@Configuration +public class WebConfiguration implements WebMvcConfigurer { + + @Override + public void configureApiVersioning(ApiVersionConfigurer configurer) { + configurer.useRequestHeader("X-API-Version"); + } +} +// end::snippet[] diff --git a/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.kt b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.kt new file mode 100644 index 00000000000..474ff85a6d2 --- /dev/null +++ b/framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigapiversion/WebConfiguration.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.docs.web.webmvc.mvcconfig.mvcconfigapiversion + +import org.springframework.context.annotation.Configuration +import org.springframework.web.servlet.config.annotation.ApiVersionConfigurer +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer + +// tag::snippet[] +@Configuration +class WebConfiguration : WebMvcConfigurer { + + override fun configureApiVersioning(configurer: ApiVersionConfigurer) { + configurer.useRequestHeader("X-API-Version") + } +} +// end::snippet[] \ No newline at end of file