I have a Spring #RestController that has a POST endpoint defined like this:
#RestController
#Validated
#RequestMapping("/example")
public class Controller {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<?> create(#Valid #RequestBody Request request,
BindingResult _unused, // DO NOT DELETE
UriComponentsBuilder uriBuilder) {
// ...
}
}
It also has an exception handler for javax.validation.ConstraintViolationException:
#ExceptionHandler({ConstraintViolationException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
ProblemDetails handleValidationError(ConstraintViolationException e) {...}
Our Spring-Boot app is using spring-boot-starter-validation for validation. The Request object uses javax.validation.* annotations to apply constraints to the various fields like this:
public class Request {
private Long id;
#Size(max = 64, message = "name length cannot exceed 64 characters")
private String name;
// ...
}
As presented above, if you POST a request with an invalid Request, the validation will throw a ConstraintViolationException, which will be handled by the exception handler. This works, we have unit tests for it, all is good.
I noticed that the BindingResult in the post method wasn't used (the name _unused and comment //DO NOT DELETE were sort of red flags.) I went ahead and deleted the parameter. All of a sudden, my tests broke -- the inbound request was still validated, but it would no longer throw a ConstraintValidationException ... now it throws a MethodArgumentNotValidException! Unfortunately I can't used this other exception because it doesn't contain the failed validation in the format that I need (and doesn't contain all the data I need either).
Why does the BindingResult presence in the argument list control which exception is thrown? How can I removed the unused variable and still throw the ConstraintViolationException when the javax.validation determines that the request body is invalid?
Spring-Boot 2.5.5
spring-boot-starter-web
spring-boot-starter-validation
OpenJDK 17.
There are two layers of the validation involves at here which happen in the following orders:
Controller layer :
enable when the controller method 's argument is annotated with #RequestBody or #ModelAttribute and with #Valid or #Validated or any annotations whose name start with "Valid" (refer this for the logic).
Based on the DataBinder stuff
Can only validate the request
In case of validation errors and there is no BindingResult argument in the controller method , throw org.springframework.web.bind.MethodArgumentNotValidException. Otherwise , continues invoking the controller method with the BindingResult arguments capturing with the validation error information.
Bean 's method layer :
enable for a spring bean if it is annotated with #Validated and the method argument or the returned value is annotated only with the bean validation annotations such as #Valid , #Size etc.
Based on the AOP stuff. The method interceptor is MethodValidationInterceptor
Can validate both the request and response
In case of validation errors ,throw javax.validation.ConstraintViolationException.
Validation in both layers at the end will delegate to the bean validation to perform the actual validation.
Because the controller is actually a spring bean , validation in both layers can take effects when invoking a controller method which is exactly demonstrated by your case with the following things happens:
DataBinder validates the request is incorrect but since the controller method has BindingResult argument , it skip throwing MethodArgumentNotValidException and continue invoking the controller method
MethodValidationInterceptor validates the request is incorrect , and throw ConstraintViolationException
The documents does not mention such behaviour clearly. I make the above summary after reading the source codes. I agree it is confusing especially in your case when validations are enable in both layers and also with the BindingResult argument. You can see that the bean validation actually validate the request for two times which sounds awkward...
So to solve your problem , you can disable the validation in controller layer 's DataBinder and always relies on the bean method level validation . You can do it by creating #ControllerAdvice with the following #InitBinder method:
#ControllerAdvice
public class InitBinderControllerAdvice {
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(null);
}
}
Then even removing BindingResult from the controller method , it should also throw out ConstraintViolationException.
I didn't know that the presence of BindingResult in controller method can modify the type of exception thrown, as I have never added it as an argument to a controller method before. What I have typically seen is the MethodArgumentNotValidException thrown for request body validation failures and ConstraintViolationException thrown for request parameter, path variable and header value violations. The format of the error details within MethodArgumentNotValidException might be different than what is in ConstraintViolationException, but it usually contains all the information you need about the error. Below is an exception handler class I wrote for your controller:
package com.example.demo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
#RestControllerAdvice
public class ControllerExceptionHandler {
public static final Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class);
#ExceptionHandler({ ConstraintViolationException.class })
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleValidationError(ConstraintViolationException exception) {
LOGGER.warn("ConstraintViolationException thrown", exception);
Map<String, Object> response = new HashMap<>();
List<Map<String, String>> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
Map<String, String> transformedError = new HashMap<>();
String fieldName = violation.getPropertyPath().toString();
transformedError.put("field", fieldName.substring(fieldName.lastIndexOf('.') + 1));
transformedError.put("error", violation.getMessage());
errors.add(transformedError);
}
response.put("errors", errors);
return response;
}
#ExceptionHandler({ MethodArgumentNotValidException.class })
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleValidationError(MethodArgumentNotValidException exception) {
LOGGER.warn("MethodArgumentNotValidException thrown", exception);
Map<String, Object> response = new HashMap<>();
if (exception.hasFieldErrors()) {
List<Map<String, String>> errors = new ArrayList<>();
for (FieldError error : exception.getFieldErrors()) {
Map<String, String> transformedError = new HashMap<>();
transformedError.put("field", error.getField());
transformedError.put("error", error.getDefaultMessage());
errors.add(transformedError);
}
response.put("errors", errors);
}
return response;
}
}
It transforms both the MethodArgumentNotValidException and ConstraintViolationException into the same error response JSON below:
{
"errors": [
{
"field": "name",
"error": "name length cannot exceed 64 characters"
}
]
}
What information were you missing in a MethodArgumentNotValidException compared to a ConstraintViolationException?
From spec:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/MethodArgumentNotValidException.html
#Valid #RequestBody Request request
If your object is not valid, we always got an MethodArgumentNotValidException. The difference here is depended on BindingResult ...
Without BindingResult, the MethodArgumentNotValidException is thrown as expected.
With BindingResult, the error will be inserted to BindingResult. We often have to check if bindresult have an error or not and do something with it.
if (bindingResult.hasErrors()) {
// handle error or create bad request status
}
BindingResult: "General interface that represents binding results. Extends the interface for error registration capabilities, allowing for a Validator to be applied, and adds binding-specific analysis and model building. "
You can double check again the errors in binding result.
I don't see an full code, so i don't know which is the cause of ConstraintViolationException, but i guess you skip the error in binding result and continue insert entity to database and violate few constrain...
Related
I've a file upload validation that raises a BindException instead of a MethodArgumentNotValidException and I don't understand why.
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'patientProfileImageDTO' on field 'profileImage': rejected value [org.springframework.web.multipart.commons.CommonsMultipartFile#2840a305]; codes [CheckImageFormat.patientProfileImageDTO.profileImage,CheckImageFormat.profileImage,CheckImageFormat.org.springframework.web.multipart.MultipartFile,CheckImageFormat]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patientProfileImageDTO.profileImage,profileImage]; arguments []; default message [profileImage]]; default message [Invalid image format (allowed: png, jpg, jpeg)]
My Controller is:
#PostMapping("/patient/image")
public ResponseEntity<?> updateProfileImage(#Validated PatientProfileImageDTO patientProfileImageDTO)
and this is the PatientProfileImageDTO
public class PatientProfileImageDTO {
#CheckImageFormat
#CheckImageSize
private MultipartFile profileImage;
public MultipartFile getProfileImage() {
return profileImage;
}
public void setProfileImage(MultipartFile profileImage) {
this.profileImage = profileImage;
}
}
the CheckFormatImage and CheckImageSize validators are correctly invoked.
I need to catch these errors in my:
#ControllerAdvice
public class ApiExceptionHandler {
ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, WebRequest request) {
...
}
}
I've other custom validation annotations in another part of my code and they work as intended.
I mean:
#OldPasswordMatch(message = "old password mismatch")
private String oldPassword;
This custom validation triggers a MethodArgumentNotValidException that what I want.
What's wrong with my code?
Thanks.
There is also a BindException thrown by Spring MVC if an invalid object was created from the request parameters. MethodArgumentNotValidException is already a subclass of BindException.
These are actually intentionally different exceptions. #ModelAttribute, which is assumed by default if no other annotation is present, goes through data binding and validation, and raises BindException to indicate a failure with binding request properties or validating the resulting values. #RequestBody, on the other hand converts the body of the request via other converter, validates it and raises various conversion related exceptions or a MethodArgumentNotValidException if validation fails. In most cases a MethodArgumentNotValidException can be handled generically (e.g. via #ExceptionHandler method) while BindException is very often handled individually in each controller method.
You can process these errors separately or you can catch only the super class BindException.
#ExceptionHandler(BindException.class)
protected ResponseEntity<Object> handleBindException(BindException ex) {
// ..
}
I have Spring Web #PostMapping endpoint that gets JSON and Jackson 2.10. should bind it to the #RequestBody DTO with couple of Enums inside. If invalid String value is passed for Enum field I get
InvalidFormatException: Cannot deserialize value of type A from String "foo": not one of the values accepted for Enum class: A
This is fine scenario, but my 400 Bad Request doesn't have any meaningful message inside.
How to provide custom response messages in 400 for each enums failing?
Example:
Valid values for transaction field are BUY and SELL
Valid values for group field are A, B, C and D
I can use maybe some javax.validation annotations but I cannot find right one.
Jackson converter class handles InvalidFormatException and throws a generic HttpMessageNotReadableException. So to customize response error message, we need to handle HttpMessageNotReadableException instead of InvalidFormatException.
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({HttpMessageNotReadableException.class})
#ResponseBody
public String handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
if(ex.getMessage().contains("Cannot deserialize value of type A")){
return "Binding failed. Allowed values are A, B and C";
} else if(ex.getMessage().contains("Cannot deserialize value of type B")){
return "Binding failed. Allowed values are 1, 2 and 3";
}
return ex.getMessage();
}
You can add global exception handler using #ControllerAdvice or add a special controller method with #ExceptionHandler annotation.
#Controller
public class SimpleController {
//other controller methods
#ExceptionHandler(InvalidFormatException.class)
public ResponseEntity<Object> errorHandler(InvalidFormatException e) {
return ResponseEntity.badRequest().body(...);
}
}
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling
UPDATE: Spring MVC's ExceptionHandlerMethodResolver (which processes #ExceptionHandler) unwraps the cause of HttpMessageNotReadableException, so it will handle InvalidFormatException: SPR-14291. Handling wrapped exceptions
I'm looking for a way that I can customize the field/object name given in the MethodArgumentNotValidException and the ConstraintViolationException.
Here's the context: I am performing validation on the server, but I want to map the server validation errors back to the input fields on the client (to show server errors directly on the input in the UI). I'm using an angular directive that can make this happen so long as I have an id that the directive recognizes from the server.
I'm currently using almost all annotations to do the server-side validation and I'd like to keep it that way. Doing the validation programmatically (through a javax.validation.Validator) would allow me to customize what gets thrown in the exception since I control how the exception is populated and thrown.
The only solution I've come up with so far is to create my own validator to replace the #Valid annotation to put in my own logic for mapping the client-side ids with the server-side fields.
But.... this doesn't feel right and there are likely hidden pitfalls in trying to do this. I also wouldn't expect that this is the first time someone needed to do this. So, smart people of the stackoverflow what say ye?
You can put your mapping in #ExceptionHandlers that handle the MethodArgumentNotValidException and the ConstraintViolationException. Here's an example with the former.
Assume an object Foo:
import javax.validation.constraints.Min;
public class Foo {
private int bar;
#Min(0)
public int getBar() {
return bar;
}
public void setBar(int bar) {
this.bar = bar;
}
}
bar needs to be non-negative. Here's the controller.
import internal.sandbox.domain.Foo;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
#Controller
public class SomeController {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Map<String,String> handleValidationFailure(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
errors.put(fieldError.getObjectName() + fieldError.getField(),
fieldError.getDefaultMessage());
}
return errors;
}
#RequestMapping(value = "/", method = RequestMethod.POST, consumes = "application/json")
public ResponseEntity<?> post(#Valid #RequestBody Foo foo) {
return new ResponseEntity<>(HttpStatus.OK);
}
}
Sending a POST with data { "bar":"-1" } throws a MethodArgumentNotValidException which is handled by the #ExceptionHandler, resulting in a 404 with the following response body:
{"foobar":"must be greater than or equal to 0"}
I'm assuming you have a much smarter #ExceptionHandler that can map your stuff better, but the exception has everything you need in it, as would the Set<ConstraintViolations> field in the ConstraintViolationException. At this point, your AngularJS service can check the rejection of the $http promise that made the POST, and have a field day processing the error information.
You can write a similar #ExceptionHandler to process ConstraintViolationExceptions thrown by Hibernate.
Suppose I have a controller:
#Controller
public class SomeController {
#RequestMapping(value = "{id}/object.do")
public String showObject(#PathVariable("id") Integer id,
HttpServletRequest request) {
//do smth
return "somePage";
}
}
When "id" is not a Number, but string like "aaa/object.do" Tomcat show me an error - "The request sent by the client was syntactically incorrect."
Is there a way to configure an error page that will be shown only when "id" path variable has incorrect type?
You can handle this with #ExceptionHandler for this particular error (I suspect it is TypeMismatchException)
#ExceptionHandler(TypeMismatchException.class)
public ModelAndView handleTypeMismatchException(TypeMismatchException ex) {
//TODO log
// Return something reasonable to the end user.
...
}
Please note, that #ExceptionHandler basically has almost the same capabilities, as usual handlers:
Much like standard controller methods annotated with a #RequestMapping annotation, the method arguments and return values of #ExceptionHandler methods are very flexible.
I'm running a webapp in Spring Web MVC 3.0 and I have a number of controller methods whose signatures are roughly as follows:
#RequestMapping(value = "/{level1}/{level2}/foo", method = RequestMethod.POST)
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#RequestParam("foo_name") String fooname,
#RequestParam(value = "description", required = false) String description);
I'd like to add some validation - for example, description should be limited to a certain length or fooname should only contain certain characters. If this validation fails, I want to return a message to the user rather than just throw some unchecked exception (which would happen anyway if I let the data percolate down to the DAO layer). I'm aware of JSR303 but have not worked with it and don't quite understand how to apply it in a Spring context.
From what I understand, another option would be to bind the #RequestBody to an entire domain object and add validation constraints there, but currently my code is set up to accept individual parameters as shown above.
What is the most straightforward way to apply validation to input parameters using this approach?
This seems to be possible now (tried with Spring 4.1.2), see https://raymondhlee.wordpress.com/2015/08/29/validating-spring-mvc-request-mapping-method-parameters/
Extract from above page:
Add MethodValidationPostProcessor to Spring #Configuration class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Add #Validated to controller class
Use #Size just before #RequestParam
#RequestMapping("/hi")
public String sayHi(#Size(max = 10, message = "name should at most 10 characters long") #RequestParam("name") String name) {
return "Hi " + name;
}
Handle ConstraintViolationException in an #ExceptionHandler method
There's nothing built in to do that, not yet anyway. With the current release versions you will still need to use the WebDataBinder to bind your parameters onto an object if you want automagic validation. It's worth learning to do if you're using SpringMVC, even if it's not your first choice for this task.
It looks something like this:
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#Valid #ModelAttribute() FooWrapper fooWrapper,
BindingResult errors) {
if (errors.hasErrors() {
//handle errors, can just return if using Spring form:error tags.
}
}
public static class FooWrapper {
#NotNull
#Size(max=32)
private String fooName;
private String description;
//getset
}
If you have Hibernate Validator 4 or later on your classpath and use the default dispatcher setup it should "Just work."
Editing since the comments were getting kind of large:
Any Object that's in your method signature that's not one of the 'expected' ones Spring knows how to inject, such as HttpRequest, ModelMap, etc, will get data bound. This is accomplished for simple cases just by matching the request param names against bean property names and calling setters. The #ModelAttribute there is just a personal style thing, in this case it isn't doing anything. The JSR-303 integration with the #Valid on a method parameter wires in through the WebDataBinder. If you use #RequestBody, you're using an object marshaller based on the content type spring determines for the request body (usually just from the http header.) The dispatcher servlet (AnnotationMethodHandlerAdapter really) doesn't have a way to 'flip the validation switch' for any arbitrary marshaller. It just passes the web request content along to the message converter and gets back a Object. No BindingResult object is generated, so there's nowhere to set the Errors anyway.
You can still just inject your validator into the controller and run it on the object you get, it just doesn't have the magic integration with the #Valid on the request parameter populating the BindingResult for you.
If you have multiple request parameters that need to be validated (with Http GET or POST). You might as well create a custom model class and use #Valid along with #ModelAttribute to validate the parameters. This way you can use Hibernate Validator or javax.validator api to validate the params. It goes something like this:
Request Method:
#RequestMapping(value="/doSomething", method=RequestMethod.GET)
public Model dosomething(#Valid #ModelAttribute ModelRequest modelRequest, BindingResult result, Model model) {
if (result.hasErrors()) {
throw new SomeException("invalid request params");
}
//to access the request params
modelRequest.getFirstParam();
modelRequest.getSecondParam();
...
}
ModelRequest class:
class ModelRequest {
#NotNull
private String firstParam;
#Size(min = 1, max = 10, message = "You messed up!")
private String secondParam;
//Setters and getters
public void setFirstParam (String firstParam) {
this.firstParam = firstParam;
}
public String getFirstParam() {
return firstParam;
}
...
}
Hope that helps.