From df2617d575d0798d6de18bb7bf9b21695bb8d209 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 5 Jul 2023 17:07:30 +0100 Subject: [PATCH] Document method validation improvements Closes gh-30643 --- framework-docs/modules/ROOT/nav.adoc | 2 + .../pages/core/validation/beanvalidation.adoc | 94 ++++++++++++--- .../web/webflux/ann-rest-exceptions.adoc | 52 ++++---- .../ann-methods/modelattrib-method-args.adoc | 13 +- .../ann-methods/multipart-forms.adoc | 6 +- .../controller/ann-methods/requestbody.adoc | 29 +++++ .../webflux/controller/ann-validation.adoc | 111 +++++++++++++++++ .../web/webmvc/mvc-ann-rest-exceptions.adoc | 22 ++-- .../web/webmvc/mvc-config/validation.adoc | 2 +- .../ann-methods/modelattrib-method-args.adoc | 19 +-- .../ann-methods/multipart-forms.adoc | 9 +- .../ann-methods/requestbody.adoc | 7 +- .../webmvc/mvc-controller/ann-validation.adoc | 112 ++++++++++++++++++ 13 files changed, 409 insertions(+), 69 deletions(-) create mode 100644 framework-docs/modules/ROOT/pages/web/webflux/controller/ann-validation.adoc create mode 100644 framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-validation.adoc diff --git a/framework-docs/modules/ROOT/nav.adoc b/framework-docs/modules/ROOT/nav.adoc index a98c10f8d2..24123d403f 100644 --- a/framework-docs/modules/ROOT/nav.adoc +++ b/framework-docs/modules/ROOT/nav.adoc @@ -267,6 +267,7 @@ ***** xref:web/webmvc/mvc-controller/ann-methods/jackson.adoc[] **** xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[] **** xref:web/webmvc/mvc-controller/ann-initbinder.adoc[] +**** xref:web/webmvc/mvc-controller/ann-validation.adoc[] **** xref:web/webmvc/mvc-controller/ann-exceptionhandler.adoc[] **** xref:web/webmvc/mvc-controller/ann-advice.adoc[] *** xref:web/webmvc-functional.adoc[] @@ -361,6 +362,7 @@ ***** xref:web/webflux/controller/ann-methods/jackson.adoc[] **** xref:web/webflux/controller/ann-modelattrib-methods.adoc[] **** xref:web/webflux/controller/ann-initbinder.adoc[] +**** xref:web/webflux/controller/ann-validation.adoc[] **** xref:web/webflux/controller/ann-exceptions.adoc[] **** xref:web/webflux/controller/ann-advice.adoc[] *** xref:web/webflux-functional.adoc[] diff --git a/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc b/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc index 3837d50a35..b8fb738128 100644 --- a/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc +++ b/framework-docs/modules/ROOT/pages/core/validation/beanvalidation.adoc @@ -123,15 +123,12 @@ Validator, is expected to be present in the classpath and is automatically detec [[validation-beanvalidation-spring-inject]] -=== Injecting a Validator +=== Inject Jakarta Validator `LocalValidatorFactoryBean` implements both `jakarta.validation.ValidatorFactory` and -`jakarta.validation.Validator`, as well as Spring's `org.springframework.validation.Validator`. -You can inject a reference to either of these interfaces into beans that need to invoke -validation logic. - -You can inject a reference to `jakarta.validation.Validator` if you prefer to work with the Bean -Validation API directly, as the following example shows: +`jakarta.validation.Validator`, so you can inject a reference to the latter to +apply validation logic if you prefer to work with the Bean Validation API directly, +as the following example shows: [tabs] ====== @@ -160,8 +157,15 @@ Kotlin:: ---- ====== -You can inject a reference to `org.springframework.validation.Validator` if your bean -requires the Spring Validation API, as the following example shows: + +[[validation-beanvalidation-spring-inject-adapter]] +=== Inject Spring Validator + +In addition to implementing `jakarta.validation.Validator`, `LocalValidatorFactoryBean` +also adapts to `org.springframework.validation.Validator`, so you can inject a reference +to the latter if your bean requires the Spring Validation API. + +For example: [tabs] ====== @@ -190,9 +194,15 @@ Kotlin:: ---- ====== +When used as `org.springframework.validation.Validator`, `LocalValidatorFactoryBean` +invokes the underlying `jakarta.validation.Validator`, and then adapts +``ContraintViolation``s to ``FieldError``s, and registers them with the `Errors` object +passed into the `validate` method. + + [[validation-beanvalidation-spring-constraints]] -=== Configuring Custom Constraints +=== Configure Custom Constraints Each bean validation constraint consists of two parts: @@ -274,9 +284,8 @@ As the preceding example shows, a `ConstraintValidator` implementation can have [[validation-beanvalidation-spring-method]] === Spring-driven Method Validation -You can integrate the method validation feature supported by Bean Validation 1.1 (and, as -a custom extension, also by Hibernate Validator 4.3) into a Spring context through a -`MethodValidationPostProcessor` bean definition: +You can integrate the method validation feature of Bean Validation into a +Spring context through a `MethodValidationPostProcessor` bean definition: [tabs] ====== @@ -305,11 +314,11 @@ XML:: ---- ====== -To be eligible for Spring-driven method validation, all target classes need to be annotated +To be eligible for Spring-driven method validation, target classes need to be annotated with Spring's `@Validated` annotation, which can optionally also declare the validation groups to use. See {api-spring-framework}/validation/beanvalidation/MethodValidationPostProcessor.html[`MethodValidationPostProcessor`] -for setup details with the Hibernate Validator and Bean Validation 1.1 providers. +for setup details with the Hibernate Validator and Bean Validation providers. [TIP] ==== @@ -320,6 +329,61 @@ xref:core/aop/proxying.adoc#aop-understanding-aop-proxies[Understanding AOP Prox to always use methods and accessors on proxied classes; direct field access will not work. ==== +By default, `jakarta.validation.ConstraintViolationException` is raised with the set of +``ConstraintViolation``s returned by `jakarata.validation.Validator`. As an alternative, +you can have `MethodValidationException` raised instead with ``ConstraintViolation``s +adapted to `MessageSourceResolvable` errors. To enable set the following flag: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; + + @Configuration + public class AppConfig { + + @Bean + public MethodValidationPostProcessor validationPostProcessor() { + MethodValidationPostProcessor processor = new MethodValidationPostProcessor(); + processor.setAdaptConstraintViolations(true); + return processor; + } + } + +---- + +XML:: ++ +[source,xml,indent=0,subs="verbatim,quotes",role="secondary"] +---- + + + +---- +====== + +`MethodValidationException` contains a list of ``ParameterValidationResult``s which +group errors by method parameter, and each exposes a `MethodParameter`, the argument +value, and a list of `MessageSourceResolvable` errors adapted from +``ConstraintViolation``s. For `@Valid` method parameters with cascaded violations on +fields and properties, the `ParameterValidationResult` is `ParameterErrors` which +implements `org.springframework.validation.Errors` and exposes validation errors as +``FieldError``s. + +The adapted `MessageSourceResolvable` errors can be turned into error messages to +display to users through the configured +xref:core/beans/context-introduction.adoc#context-functionality-messagesource[`MessageSource`] +based on locale and language specific resource bundles. + +NOTE: Spring MVC and WebFlux have built-in support for method validation, and therefore +for web controller methods there is no need for a class level `@Validated` and an AOP proxy. +See the Spring MVC xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation] section, +the WebFlux xref:web/webflux/controller/ann-validation.adoc[Validation] section, +and the xref:web/webmvc/mvc-controller/ann-validation.adoc[Error Responses] section. + diff --git a/framework-docs/modules/ROOT/pages/web/webflux/ann-rest-exceptions.adoc b/framework-docs/modules/ROOT/pages/web/webflux/ann-rest-exceptions.adoc index 5be40fd19a..ec887afa1c 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/ann-rest-exceptions.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/ann-rest-exceptions.adoc @@ -67,23 +67,23 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an [[webflux-ann-rest-exceptions-i18n]] -== Internationalization +== Customization and i18n [.small]#xref:web/webmvc/mvc-ann-rest-exceptions.adoc#mvc-ann-rest-exceptions-i18n[See equivalent in the Servlet stack]# -It is a common requirement to internationalize error response details, and good practice -to customize the problem details for Spring WebFlux exceptions. This section describes the -support for that. +It is a common requirement to customize and internationalize error response details. +It is also good practice to customize the problem details for Spring WebFlux exceptions +to avoid revealing implementation details. This section describes the support for that. -`ErrorResponse` exposes message codes for "type", "title", and "detail", in addition to +An `ErrorResponse` exposes message codes for "type", "title", and "detail", as well as message code arguments for the "detail" field. `ResponseEntityExceptionHandler` resolves these through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource] -and updates the `ProblemDetail` accordingly. +and updates the corresponding `ProblemDetail` fields accordingly. The default strategy for message codes follows the pattern: `problemDetail.[type|title|detail].[fully qualified exception class name]` -Some `ErrorResponse` may expose more than one message code, typically adding a suffix +An `ErrorResponse` may expose more than one message code, typically adding a suffix to the default message code. The table below lists message codes, and arguments for Spring WebFlux exceptions: @@ -92,28 +92,19 @@ Spring WebFlux exceptions: |=== | Exception | Message Code | Message Code Arguments -| `UnsupportedMediaTypeStatusException` +| `HandlerMethodValidationException` | (default) -| `+{0}+` the media type that is not supported, `+{1}+` list of supported media types +| `+{0}+` list all validation errors. +Message codes and arguments for each error are also resolved via `MessageSource`. -| `UnsupportedMediaTypeStatusException` -| (default) + ".parseError" -| +| `MethodNotAllowedException` +| (default) +| `+{0}+` the current HTTP method, `+{1}+` the list of supported HTTP methods | `MissingRequestValueException` | (default) | `+{0}+` a label for the value (e.g. "request header", "cookie value", ...), `+{1}+` the value name -| `UnsatisfiedRequestParameterException` -| (default) -| `+{0}+` the list of parameter conditions - -| `WebExchangeBindException` -| (default) -| `+{0}+` the list of global errors, `+{1}+` the list of field errors. -Message codes and arguments for each error within the `BindingResult` are also resolved -via `MessageSource`. - | `NotAcceptableStatusException` | (default) | `+{0}+` list of supported media types @@ -126,9 +117,22 @@ via `MessageSource`. | (default) | `+{0}+` the failure reason provided to the class constructor -| `MethodNotAllowedException` +| `UnsupportedMediaTypeStatusException` | (default) -| `+{0}+` the current HTTP method, `+{1}+` the list of supported HTTP methods +| `+{0}+` the media type that is not supported, `+{1}+` list of supported media types + +| `UnsupportedMediaTypeStatusException` +| (default) + ".parseError" +| + +| `UnsatisfiedRequestParameterException` +| (default) +| `+{0}+` the list of parameter conditions + +| `WebExchangeBindException` +| (default) +| `+{0}+` the list of global errors, `+{1}+` the list of field errors. +Message codes and arguments for each error are also resolved via `MessageSource`. |=== diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc index 347a025545..809104fe1e 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/modelattrib-method-args.adoc @@ -160,10 +160,13 @@ Kotlin:: ---- ====== -Note that use of `@ModelAttribute` is optional -- for example, to set its attributes. -By default, any argument that is not a simple value type (as determined by -{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]) -and is not resolved by any other argument resolver is treated as if it were annotated -with `@ModelAttribute`. +If method validation applies because other parameters have `@Constraint` annotations, +then `HandlerMethodValidationException` would be raised instead. See the section on +controller method xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]. + +TIP: Using `@ModelAttribute` is optional. By default, any argument that is not a simple +value type as determined by +{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty] +_AND_ that is not resolved by any other argument resolver is treated as an `@ModelAttribute`. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc index 3ded73de6d..462bf3eccb 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/multipart-forms.adoc @@ -176,6 +176,10 @@ Kotlin:: ====== -- +If method validation applies because other parameters have `@Constraint` annotations, +then `HandlerMethodValidationException` is raised instead. See the section on +xref:web/webflux/controller/ann-validation.adoc[Validation]. + To access all multipart data as a `MultiValueMap`, you can use `@RequestBody`, as the following example shows: @@ -306,5 +310,3 @@ file upload. Received part events can also be relayed to another service by using the `WebClient`. See xref:web/webflux-webclient/client-body.adoc#webflux-client-body-multipart[Multipart Data]. - - diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestbody.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestbody.adoc index 8190c28112..b4b78afd28 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestbody.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-methods/requestbody.adoc @@ -89,4 +89,33 @@ Kotlin:: ---- ====== +You can also declare an `Errors` parameter for access to validation errors, but in +that case the request body must not be a `Mono`, and will be resolved first: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + @PostMapping("/accounts") + public void handle(@Valid @RequestBody Account account, Errors errors) { + // use one of the onError* operators... + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + @PostMapping("/accounts") + fun handle(@Valid @RequestBody account: Mono) { + // ... + } +---- +====== + +If method validation applies because other parameters have `@Constraint` annotations, +then `HandlerMethodValidationException` is raised instead. For more details, see the +section on xref:web/webflux/controller/ann-validation.adoc[Validation]. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-validation.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-validation.adoc new file mode 100644 index 0000000000..8b0c069db6 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-validation.adoc @@ -0,0 +1,111 @@ +[[mvc-ann-validation]] += Validation + +[.small]#xref:web/webmvc/mvc-controller/ann-validation.adoc[See equivalent in the Servlet stack]# + +Spring WebFlux has built-in xref:core/validation/validator.adoc[Validation] support for +`@RequestMapping` methods, including the option to use +xref:core/validation/beanvalidation.adoc[Java Bean Validation]. +The validation support works on two levels. + +First, method parameters such as +xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute], +xref:web/webflux/controller/ann-methods/requestbody.adoc[@RequestBody], and +xref:web/webflux/controller/ann-methods/multipart-forms.adoc[@RequestPart] do perform +validation if annotated with Jakarta's `@Valid` or Spring's `@Validated` annotation, and +raise `MethodArgumentNotValidException` in case of validation errors. If you want to handle +the errors in the controller method instead, you can declare an `Errors` or `BindingResult` +method parameter immediately after the validated parameter. + +Second, if https://beanvalidation.org/[Java Bean Validation] is present _AND_ other method +parameters, e.g. `@RequestHeader`, `@RequestParam`, `@PathVariable` have `@Constraint` +annotations, then method validation is applied to all method arguments, raising +`HandlerMethodValidationException` in case of validation errors. You can still declare an +`Errors` or `BindingResult` after an `@Valid` method parameter, and handle validation +errors within the controller method, as long as there are no validation errors on other +method arguments. + +You can configure a `Validator` globally through the +xref:web/webflux/config.adoc#webflux-config-validation[WebMvc config], or locally +through an xref:web/webflux/controller/ann-initbinder.adoc[@InitBinder] method in an +`@Controller` or `@ControllerAdvice`. You can also use multiple validators. + +NOTE: If a controller has a class level `@Validated`, then +xref:core/validation/beanvalidation.adoc#validation-beanvalidation-spring-method[method validation is applied] +through an AOP proxy. In order to take advantage of the Spring MVC built-in support for +method validation added in Spring Framework 6.1, you need to remove the class level +`@Validated` annotation from the controller. + +The xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses] section provides further +details on how `MethodArgumentNotValidException` and `HandlerMethodValidationException` +are handled, and also how their rendering can be customized through a `MessageSource` and +locale and language specific resource bundles. + +For further custom handling of method validation errors, you can extend +`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller +or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly. +The exception contains a list of``ParameterValidationResult``s that group validation errors +by method parameter. You can either iterate over those, or provide a visitor with callback +methods by controller method parameter type: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + HandlerMethodValidationException ex = ... ; + + ex.visitResults(new HandlerMethodValidationException.Visitor() { + + @Override + public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) { + // ... + } + + @Override + public void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result) { + // ... + } + + @Override + public void modelAttribute(@Nullable ModelAttribute modelAttribute, ParameterErrors errors) { + + // ... + + @Override + public void other(ParameterValidationResult result) { + // ... + } + }); +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + // HandlerMethodValidationException + val ex + + ex.visitResults(object : HandlerMethodValidationException.Visitor { + + override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) { + // ... + } + + override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) { + // ... + } + + override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) { + // ... + } + + // ... + + override fun other(result: ParameterValidationResult) { + // ... + } + }) +---- +====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-rest-exceptions.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-rest-exceptions.adoc index 79329becc2..1f4d9ff1fd 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-rest-exceptions.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-ann-rest-exceptions.adoc @@ -67,23 +67,23 @@ from an existing `ProblemDetail`. This could be done centrally, e.g. from an [[mvc-ann-rest-exceptions-i18n]] -== Internationalization +== Customization and i18n [.small]#xref:web/webflux/ann-rest-exceptions.adoc#webflux-ann-rest-exceptions-i18n[See equivalent in the Reactive stack]# -It is a common requirement to internationalize error response details, and good practice -to customize the problem details for Spring WebFlux exceptions. This section describes the -support for that. +It is a common requirement to customize and internationalize error response details. +It is also good practice to customize the problem details for Spring MVC exceptions +to avoid revealing implementation details. This section describes the support for that. -`ErrorResponse` exposes message codes for "type", "title", and "detail", in addition to +An `ErrorResponse` exposes message codes for "type", "title", and "detail", as well as message code arguments for the "detail" field. `ResponseEntityExceptionHandler` resolves these through a xref:core/beans/context-introduction.adoc#context-functionality-messagesource[MessageSource] -and updates the `ProblemDetail` accordingly. +and updates the corresponding `ProblemDetail` fields accordingly. The default strategy for message codes follows the pattern: `problemDetail.[type|title|detail].[fully qualified exception class name]` -Some `ErrorResponse` may expose more than one message code, typically adding a suffix +An `ErrorResponse` may expose more than one message code, typically adding a suffix to the default message code. The table below lists message codes, and arguments for Spring MVC exceptions: @@ -100,6 +100,11 @@ Spring MVC exceptions: | (default) | `+{0}+` property name, `+{1}+` property value +| `HandlerMethodValidationException` +| (default) +| `+{0}+` list all validation errors. +Message codes and arguments for each error are also resolved via `MessageSource`. + | `HttpMediaTypeNotAcceptableException` | (default) | `+{0}+` list of supported media types @@ -131,8 +136,7 @@ Spring MVC exceptions: | `MethodArgumentNotValidException` | (default) | `+{0}+` the list of global errors, `+{1}+` the list of field errors. - Message codes and arguments for each error within the `BindingResult` are also resolved - via `MessageSource`. + Message codes and arguments for each error are also resolvedvia `MessageSource`. | `MissingRequestHeaderException` | (default) diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/validation.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/validation.adoc index 833914f4ab..b307b8fc8c 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/validation.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/validation.adoc @@ -6,7 +6,7 @@ By default, if xref:core/validation/beanvalidation.adoc#validation-beanvalidation-overview[Bean Validation] is present on the classpath (for example, Hibernate Validator), the `LocalValidatorFactoryBean` is registered as a global xref:core/validation/validator.adoc[Validator] for use with `@Valid` and -`Validated` on controller method arguments. +`@Validated` on controller method arguments. In Java configuration, you can customize the global `Validator` instance, as the following example shows: diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc index 1136ab9495..1add700c9e 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc @@ -144,7 +144,7 @@ Java:: } @PostMapping("update") - public String update(@Valid AccountForm form, BindingResult result, + public String update(AccountForm form, BindingResult result, @ModelAttribute(binding=false) Account account) { // <1> // ... } @@ -166,7 +166,7 @@ Kotlin:: } @PostMapping("update") - fun update(@Valid form: AccountForm, result: BindingResult, + fun update(form: AccountForm, result: BindingResult, @ModelAttribute(binding = false) account: Account): String { // <1> // ... } @@ -210,10 +210,15 @@ Kotlin:: <1> Validate the `Pet` instance. ====== -Note that using `@ModelAttribute` is optional (for example, to set its attributes). -By default, any argument that is not a simple value type (as determined by -{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty]) -and is not resolved by any other argument resolver is treated as if it were annotated -with `@ModelAttribute`. +If an `@ModelAttribute` is declared without `BindingResult` parameter after it, then +`MethodArgumentNotValueException` is raised. However, if method validation applies because +other parameters have `@Constraint` annotations, then `HandlerMethodValidationException` +is raised instead. For more details, see the section on +xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]. + +TIP: Using `@ModelAttribute` is optional. By default, any parameter that is not a simple +value type as determined by +{api-spring-framework}/beans/BeanUtils.html#isSimpleProperty-java.lang.Class-[BeanUtils#isSimpleProperty] +_AND_ that is not resolved by any other argument resolver is treated as an `@ModelAttribute`. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc index de91ecad48..5e4addcb3a 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc @@ -188,8 +188,7 @@ Java:: [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- @PostMapping("/") - public String handle(@Valid @RequestPart("meta-data") MetaData metadata, - BindingResult result) { + public String handle(@Valid @RequestPart("meta-data") MetaData metadata, Errors errors) { // ... } ---- @@ -199,12 +198,14 @@ Kotlin:: [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ---- @PostMapping("/") - fun handle(@Valid @RequestPart("meta-data") metadata: MetaData, - result: BindingResult): String { + fun handle(@Valid @RequestPart("meta-data") metadata: MetaData, errors: Errors): String { // ... } ---- ====== +If method validation applies because other parameters have `@Constraint` annotations, +then `HandlerMethodValidationException` is raised instead. For more details, see the +section on xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestbody.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestbody.adoc index 2c4f316feb..9cdc1e8fb0 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestbody.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/requestbody.adoc @@ -48,7 +48,7 @@ Java:: [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- @PostMapping("/accounts") - public void handle(@Valid @RequestBody Account account, BindingResult result) { + public void handle(@Valid @RequestBody Account account, Errors errors) { // ... } ---- @@ -58,10 +58,13 @@ Kotlin:: [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ---- @PostMapping("/accounts") - fun handle(@Valid @RequestBody account: Account, result: BindingResult) { + fun handle(@Valid @RequestBody account: Account, errors: Errors) { // ... } ---- ====== +If method validation applies because other parameters have `@Constraint` annotations, +then `HandlerMethodValidationException` is raised instead. For more details, see the +section on xref:web/webmvc/mvc-controller/ann-validation.adoc[Validation]. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-validation.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-validation.adoc new file mode 100644 index 0000000000..a1528b5534 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-validation.adoc @@ -0,0 +1,112 @@ +[[mvc-ann-validation]] += Validation + +[.small]#xref:web/webflux/controller/ann-validation.adoc[See equivalent in the Reactive stack]# + +Spring MVC has built-in xref:core/validation/validator.adoc[Validation] support for +`@RequestMapping` methods, including the option to use +xref:core/validation/beanvalidation.adoc[Java Bean Validation]. +The validation support works on two levels. + +First, method parameters such as +xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute], +xref:web/webmvc/mvc-controller/ann-methods/requestbody.adoc[@RequestBody], and +xref:web/webmvc/mvc-controller/ann-methods/multipart-forms.adoc[@RequestPart] do perform +validation if annotated with Jakarta's `@Valid` or Spring's `@Validated` annotation, and +raise `MethodArgumentNotValidException` in case of validation errors. If you want to handle +the errors in the controller method instead, you can declare an `Errors` or `BindingResult` +method parameter immediately after the validated parameter. + +Second, if https://beanvalidation.org/[Java Bean Validation] is present _AND_ other method +parameters, e.g. `@RequestHeader`, `@RequestParam`, `@PathVariable` have `@Constraint` +annotations, then method validation is applied to all method arguments, raising +`HandlerMethodValidationException` in case of validation errors. You can still declare an +`Errors` or `BindingResult` after an `@Valid` method parameter, and handle validation +errors within the controller method, as long as there are no validation errors on other +method arguments. Method validation is also applied to the return value if the method +is annotated with `@Valid` or has other `@Constraint` annotations. + +You can configure a `Validator` globally through the +xref:web/webmvc/mvc-config/validation.adoc[WebMvc config], or locally through an +xref:web/webmvc/mvc-controller/ann-initbinder.adoc[@InitBinder] method in an +`@Controller` or `@ControllerAdvice`. You can also use multiple validators. + +NOTE: If a controller has a class level `@Validated`, then +xref:core/validation/beanvalidation.adoc#validation-beanvalidation-spring-method[method validation is applied] +through an AOP proxy. In order to take advantage of the Spring MVC built-in support for +method validation added in Spring Framework 6.1, you need to remove the class level +`@Validated` annotation from the controller. + +The xref:web/webmvc/mvc-ann-rest-exceptions.adoc[Error Responses] section provides further +details on how `MethodArgumentNotValidException` and `HandlerMethodValidationException` +are handled, and also how their rendering can be customized through a `MessageSource` and +locale and language specific resource bundles. + +For further custom handling of method validation errors, you can extend +`ResponseEntityExceptionHandler` or use an `@ExceptionHandler` method in a controller +or in a `@ControllerAdvice`, and handle `HandlerMethodValidationException` directly. +The exception contains a list of``ParameterValidationResult``s that group validation errors +by method parameter. You can either iterate over those, or provide a visitor with callback +methods by controller method parameter type: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + HandlerMethodValidationException ex = ... ; + + ex.visitResults(new HandlerMethodValidationException.Visitor() { + + @Override + public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) { + // ... + } + + @Override + public void requestParam(@Nullable RequestParam requestParam, ParameterValidationResult result) { + // ... + } + + @Override + public void modelAttribute(@Nullable ModelAttribute modelAttribute, ParameterErrors errors) { + + // ... + + @Override + public void other(ParameterValidationResult result) { + // ... + } + }); +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + // HandlerMethodValidationException + val ex + + ex.visitResults(object : HandlerMethodValidationException.Visitor { + + override fun requestHeader(requestHeader: RequestHeader, result: ParameterValidationResult) { + // ... + } + + override fun requestParam(requestParam: RequestParam?, result: ParameterValidationResult) { + // ... + } + + override fun modelAttribute(modelAttribute: ModelAttribute?, errors: ParameterErrors) { + // ... + } + + // ... + + override fun other(result: ParameterValidationResult) { + // ... + } + }) +---- +====== \ No newline at end of file