Let's Build an API Together - Part 33

16.02.2018 by Jens in API Series | APIs | Newsletter

In the last recap session, we take a look at doing validations.

Field and object validation is easy in Spring. Just add @Valid to your parameter and Spring will validate it against any javax.validation annotation present on the object or its children.

So, in the board controller I added it like:

@PostMapping("/boards")
@ResponseStatus(code=HttpStatus.CREATED)
public Board createBoard( @Valid @RequestBody Board board) {
    return service.createBoard(board);
}

Board has two valdiations on the name field like:

@Column(length=100)
@NotEmpty
@Size(min=6, max=100)
private String name;

@NotEmpty check that the field is not null, not an empty string (does even do a tail). @Size check that the length of the name is between 6 and 100 characters long.

When we send now a request with an empty name, Spring will run the validation and comes back with a MethodArgumentNotValidException. We can catch this one in the @ControllerAdvice too by adding:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> handleException(MethodArgumentNotValidException exception) {

    final List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();

    Map<String, ValidationError> tempErrors = new HashMap<String, ValidationError>();
    for (FieldError fieldError : fieldErrors) {
        if (!tempErrors.containsKey(fieldError.getField())) {
            tempErrors.put(fieldError.getField(), new ValidationError(fieldError.getField()));
        }

        tempErrors.get(fieldError.getField()).getMsg().add(fieldError.getDefaultMessage());
    }

    final ValidationApiError msg = new ValidationApiError();
    msg.setMsgCode(MessageCode.USER_INVALID);
    msg.setValidationErrors(tempErrors.values());

    return new ResponseEntity<ApiError>(msg, HttpStatus.UNPROCESSABLE_ENTITY);
}

MethodArgumentNotValidException also includes the so-called binding result, where Spring keeps track of property binding and validation. We can access all field errors from it, aka name was empty, map them to our output and return a useful response to the client.

A client could now automatically use the response and present error messages in its UI to the user. I’ve done that a couple of times where we also handled the clients, and it worked pretty well. However, it also has a dark side. If you mix simple input field validations in the backend, business exceptions, and frontend validation, it can become messy to handle it over the same mechanism. For example, in one project the client’s validations were all driven by the responses of the backend, so to add a new client-side validation, we had to add the check in the backend and deploy it.

It is a tradeoff like any other design decision too. However, never skip server-side input validation - never trust the client :-)


Want content like this in your inbox each workday? No BS, spam or tricks... just useful content:

I understand and agree to the privacy policy