Validation Error Response

Returning Field Errors

Clients (and your React form) need to know which field failed and why. Handle MethodArgumentNotValidException and return the field errors. (The next module covers @RestControllerAdvice in depth.)

@RestControllerAdvice
public class ValidationErrorHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ProblemDetail handle(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new LinkedHashMap<>();
        ex.getBindingResult().getFieldErrors()
          .forEach(e -> errors.putIfAbsent(e.getField(), e.getDefaultMessage()));

        ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Request has invalid fields");
        pd.setTitle("Validation failed");
        pd.setProperty("errors", errors);
        return pd;
    }
}

Example

curl -i -X POST localhost:8080/api/products -H "Content-Type: application/json" -d '{"name":"","price":-5,"stock":10,"category":""}'

Example output

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{"type":"about:blank","title":"Validation failed","status":400,"detail":"Request has invalid fields","instance":"/api/products","errors":{"name":"must not be blank","price":"must be greater than or equal to 0.01","category":"category is required"}}

Note: The order of field errors is not guaranteed by Hibernate Validator. If the order matters to you (for tests), sort the field names.

Custom Messages

Put messages in src/main/resources/ValidationMessages.properties to keep them out of code (and translate them later):

Example

ValidationMessages.properties

product.name.required=Please enter a product name
product.price.min=Price must be at least {value}
@NotBlank(message = "{product.name.required}") String name,
@DecimalMin(value = "0.01", message = "{product.price.min}") BigDecimal price