Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top