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) {
// ..
}
Related
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...
when I use the Objects.requireNonNull() to validate the request body, some api return error code 400, others return 500. It should be 400, because the body sent to server side is invalid. I am now confused, do anyone know how to address this. Thanks!
#PostMapping(value = "/referencing-courses")
public Object get(#RequestBody Criteria criteria) {
Objects.requireNonNull(criteria, "body is required");
Objects.requireNonNull(criteria.getContentId(), "contentId is required.");
Objects.requireNonNull(criteria.getSchemaVersion(), "schemaVersion is required.");
return findLatestTreeRevisionReference.find(criteria);
}
Objects.requireNonNull() throws a `NullPointerException if the passed parameter is null. Exceptions trigger an Internal Server Error (500).
The status code 400 is not caused by the exception but because controller parameters are not null by default and spring validates this.
As #chrylis -cautiouslyoptimistic- pointed out in the comments, you can use #Valid:
#PostMapping(value = "/referencing-courses")
public Object get(#RequestBody #Valid Criteria criteria) {
return findLatestTreeRevisionReference.find(criteria);
}
For this to work, Criteria needs to use proper annotations:
public class Criteria{
#NotNull
private String contentId;
#NotNull
private String schemaVersion;
//Getters/Setters
}
You can also create a custom exception and/or exception handler in your controller as described here if #Valid is not viable.
For example, you could catch every NullPointerException in your controller and send back a 400 Bad Request status:
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(NullPointerException.class)
public void handleNPE() {
//Done by #ResponseStatus
}
However, NullPointerExceptions might occur because of different reasons than the user sending an invalid request. In order to bypass that issue, you can create your own exception that translates to the error 400:
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Request body invalid")
public class InvalidBodyException extends RuntimeException {
//No need for additional logic
}
You can then make your own method that does the null check and use that instead of Objects.requireNonNull:
public void requireNonNull(Object o){
if(o==null){
throw new InvalidBodyException();
}
}
My requirement is to get the json response with customized error message when a required #RequestParam is not sent to the request handler or invalid parameter(required is int but user is passing string) is sent to the request handler.
currently I am trying to use the #Exceptionhandler mechanism to handle these exceptions. But the respective exception handler methods not getting invoked.
Please see the code snippet:
#Controller
#RequestMapping("api/v1/getDetails")
public class Abc {
#RequestMapping
#ResponseBody
public Envelope<Object> retrieveTransactions(#RequestParam(required = false) Integer a,
#RequestParam int b, #RequestParam(required = false) boolean c,
HttpServletRequest req) {`
//implementation goes here
}
#ExceptionHandler(MissingServletRequestParameterException.class)
#ResponseBody
public Envelope<Object> missingParameterExceptionHandler(Exception exception,
HttpServletRequest request) {
Envelope<Object> envelope = null;
//error implementation
return envelope;
}
#ExceptionHandler(TypeMismatchException.class)
#ResponseBody
public Envelope<Object> typeMismatchExpcetionHandler(Exception exception, HttpServletRequest request) {
Envelope<Object> envelope = null;
//error implementation
return envelope;
}
Do I need to configure anything extra for exception handler? can anyone tell me where I am doing the wrong.
Consider identifying the parameter name in the RequestParameter annotation.
For example
#RequestParam(value="blammy", required=false)
I've never bothered figuring out how to handle type mismatch,
instead I've found it easier to accept all parameters as String and perform all verification myself (including type).
Also,
If you are accepting the HttpServletRequest as a parameter to your handler,
then there is no need to use #RequestParam annotations,
just get the parameter values directly from the request.
Finally,
consider org.springframework.web.context.request.WebRequest
or org.springframework.web.context.request.NativeWebRequest
instead of HttpServletRequest.
Have you tried to use MethodArgumentNotValidException or HttpMessageNotReadableException instead on your handlers?
And put required = true on your #RequestParam declaration to catch missing params exceptions
#RequestParam(required = true)
My controller looks like the following:
#RequestMapping(value = "/cars/{types}", method = RequestMethod.PUT,
headers = "Accept=application/json")
#ResponseStatus(HttpStatus.OK)
public void startEngine(
#PathVariable #Min(0) String types, #RequestBody #Valid someObject request, BindingResult result)
throws MethodArgumentNotValidException {
if(result.hasErrors())
{
System.out.println("Error");
//Should I be throwing MethodArgumentNotValidException here? And if so how? I don't know how to retrieve the first parameter for it's constructor (MethodParameter object)
}
//Controller code
}
So after I verify whether or not my result object encountered any errors during validation, how can I then throw the MethodArgumentNotValidException? Or should Spring be already throwing that exception during validation?
If I remember correctly, Spring should throw MethodArgumentNotValidException only if you have not provided an Errors (here, BindingResult) parameter for the #Valid annotated parameter.
You can throw it yourself if you would like to.
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.