16

I have created a Spring MVC REST service using Bean Validation 1.2 with the following method:

@RequestMapping(value = "/valid")
public String validatedMethod(@Valid ValidObject object) {

}

If object isn't valid, Tomcat informs me that The request sent by the client was syntactically incorrect. and my validatedMethod is never called.

How can I get the message that was defined in the ValidObject bean? Should I use some filter or interceptor?

I know that I can rewrite like below, to get the set of ConstraintViolations from the injected Validator, but the above seems more neat...

@RequestMapping(value = "/valid")
public String validatedMethod(ValidObject object) {
    Set<ConstraintViolation<ValidObject>> constraintViolations = validator
            .validate(object);
    if (constraintViolations.isEmpty()) {
        return "valid";
    } else {
        final StringBuilder message = new StringBuilder();
        constraintViolations.forEach((action) -> {
            message.append(action.getPropertyPath());
            message.append(": ");
            message.append(action.getMessage());
        });
        return message.toString();
    }
}
Dormouse
  • 1,617
  • 1
  • 23
  • 33

4 Answers4

32

I believe a better way of doing this is using ExceptionHandler.

In your Controller you can write ExceptionHandler to handle different exceptions. Below is the code for the same:

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ValidationFailureResponse validationError(MethodArgumentNotValidException ex) {
    BindingResult result = ex.getBindingResult();
    final List<FieldError> fieldErrors = result.getFieldErrors();

    return new ValidationFailureResponse((FieldError[])(fieldErrors.toArray(new FieldError[fieldErrors.size()])));
}

When you send a bad request to the Controller, the validator throws an exception of type MethodArgumentNotValidException. So the ideal way would be to write an exception handler to specifically handle this exception.

There you can create a beautiful response to tell the user of things which went wrong. I advocate this, because you have to write this just once and many Controller methods can use it. :)

UPDATE

When you use the @Valid annotation for a method argument in the Controller, the validator is invoked automatically and it tries to validate the object, if the object is invalid, it throws MethodArgumentNotValidException.

If Spring finds an ExceptionHandler method for this exception it will execute the code inside this method.

You just need to make sure that the method above is present in your Controller.

Now there is another case when you have multiple Controllers where you want to validate the method arguments. In this case I suggest you to create a ExceptionResolver class and put this method there. Make your Controllers extend this class and your job is done.

dharam
  • 7,882
  • 15
  • 65
  • 93
  • Ok, this seems DRY. Could you elaborate a bit more? How do I wire this up to my `validatedMethod`? Oh, and the method is lacking a name. – Dormouse Jul 17 '14 at 19:45
  • `ValidationFailureResponse` seems to not be in any of my dependencies, and Google doesn't think it exists. Which Maven dependency could I add? – Dormouse Jul 17 '14 at 19:59
  • 2
    this is your custom response class. I said you can fabricate and send any response object you want :) – dharam Jul 17 '14 at 20:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/57531/discussion-between-dormouse-and-dharam). – Dormouse Jul 17 '14 at 20:09
  • Referring to your update: You can now use `@ControllerAdvice` to define define global exception handlers. – fridberg Jun 08 '17 at 08:35
  • Is there way I can produce the result of `Set> constraintViolations = validator .validate(object);` using `@Valid` in `MethodArgumentNotValidExeption` ?? I get only one field which is being voilated, but I want all of the fields which are voilated. – Rookie007 Oct 20 '21 at 07:04
7

Try this

@RequestMapping(value = "/valid")
public String validatedMethod(@Valid ValidObject object, BindingResult result) {
    StringBuilder builder = new StringBuilder();
    List<FieldError> errors = result.getFieldErrors();
    for (FieldError error : errors ) {
       builder.append(error.getField() + " : " + error.getDefaultMessage());
    } 
    return builder.toString();
}
Thiru
  • 33
  • 7
usha
  • 28,973
  • 5
  • 72
  • 93
  • Cheers, works like a charm :) Would be nice though, not having to do this for every validated method. – Dormouse Jul 17 '14 at 19:34
3

When you use @Valid and doing bad request body Spring handle MethodArgumentNotValidException You must create special class and extend ResponseEntityExceptionHandler and override handleMethodArgumentNotValid Example

@ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(UserExistException.class)
  public ResponseEntity<Object> handleUserExistException(
        UserExistException e, WebRequest request) {

    Map<String, Object> body = new LinkedHashMap<>();
    body.put("timestamp", LocalDateTime.now());
    body.put("status", HttpStatus.BAD_REQUEST.value());
    body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
    body.put("message", e.getMessage());
    body.put("path", request.getDescription(false).replace("uri=", ""));


    return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
  }

  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

    Map<String, Object> body = new LinkedHashMap<>();
    body.put("timestamp", LocalDateTime.now());
    body.put("status", HttpStatus.BAD_REQUEST.value());
    body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
    body.put("path", request.getDescription(false).replace("uri=", ""));
    return new ResponseEntity<>(body, headers, status);
  }
}
Michael Laffargue
  • 10,116
  • 6
  • 42
  • 76
  • Yes, good addition. I use `@ControllerAdvice` for some time now, to avoid code duplication and to have all Exception based response handling in one place. – Dormouse Jul 24 '22 at 21:24
0

The answer by @dharam works. For users at Spring v4.3, Here's a nice implementation which uses a Custom Exception class to handle exception by type.

@RestControllerAdvice
public class CustomExceptionClass extends ResponseEntityExceptionHandler{
   @ExceptionHandler(value = MethodArgumentNotValidException.class)
   public ResponseEntity<Object> handleException(MethodArgumentNotValidException ex, WebRequest req){
   // Build your custom response object and access the exception message using ex.getMessage()
}
}

This method will enable handling all @Valid exceptions across all of your @Controller methods in a consolidated way

user3280711
  • 43
  • 1
  • 12