Spring Boot binding and validation error handling in REST controller

Jaap van Hengstum picture Jaap van Hengstum · Jan 11, 2016 · Viewed 55k times · Source

When I have the following model with JSR-303 (validation framework) annotations:

public enum Gender {
    MALE, FEMALE
}

public class Profile {
    private Gender gender;

    @NotNull
    private String name;

    ...
}

and the following JSON data:

{ "gender":"INVALID_INPUT" }

In my REST controller, I want to handle both the binding errors (invalid enum value for gender property) and validation errors (name property cannot be null).

The following controller method does NOT work:

@RequestMapping(method = RequestMethod.POST)
public Profile insert(@Validated @RequestBody Profile profile, BindingResult result) {
    ...
}

This gives com.fasterxml.jackson.databind.exc.InvalidFormatException serialization error before binding or validation takes place.

After some fiddling, I came up with this custom code which does what I want:

@RequestMapping(method = RequestMethod.POST)
public Profile insert(@RequestBody Map values) throws BindException {

    Profile profile = new Profile();

    DataBinder binder = new DataBinder(profile);
    binder.bind(new MutablePropertyValues(values));

    // validator is instance of LocalValidatorFactoryBean class
    binder.setValidator(validator);
    binder.validate();

    // throws BindException if there are binding/validation
    // errors, exception is handled using @ControllerAdvice.
    binder.close(); 

    // No binding/validation errors, profile is populated 
    // with request values.

    ...
}

Basically what this code does, is serialize to a generic map instead of model and then use custom code to bind to model and check for errors.

I have the following questions:

  1. Is custom code the way to go here or is there a more standard way of doing this in Spring Boot?
  2. How does the @Validated annotation work? How can I make my own custom annotation that works like @Validated to encapsulate my custom binding code?

Answer

Ali Dehghani picture Ali Dehghani · Jan 11, 2016

Usually when Spring MVC fails to read the http messages (e.g. request body), it will throw an instance of HttpMessageNotReadableException exception. So, if spring could not bind to your model, it should throw that exception. Also, if you do NOT define a BindingResult after each to-be-validated model in your method parameters, in case of a validation error, spring will throw a MethodArgumentNotValidException exception. With all this, you can create ControllerAdvice that catches these two exceptions and handles them in your desirable way.

@ControllerAdvice(annotations = {RestController.class})
public class UncaughtExceptionsControllerAdvice {
    @ExceptionHandler({MethodArgumentNotValidException.class, HttpMessageNotReadableException.class})
    public ResponseEntity handleBindingErrors(Exception ex) {
        // do whatever you want with the exceptions
    }
}