Card 04/ 06
Turning Validation Errors Into a Field-by-Field Response
Catch the exception @Valid throws and tell the client exactly which fields to fix.
When @Valid rejects a request, Spring throws MethodArgumentNotValidException, which carries every field that broke a rule and the message for each. Handling that exception yourself turns a bare 400 into a response that lists each field and what is wrong with it.
You need a controller that uses @Valid, the annotation that makes Spring check a parameter's constraints before calling the method. The handler below goes in the controller itself, or in a class annotated @RestControllerAdvice, which applies its handlers to every controller.
From a failed validation to a field-by-field response
Catch MethodArgumentNotValidException
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {@ExceptionHandler tells Spring to call this method whenever that exception is thrown while handling a request, instead of answering with its default 400.
Read the field errors out of its BindingResult
Map<String, String> errors = new HashMap<>();
for (var error : ex.getBindingResult().getFieldErrors()) {
errors.put(error.getField(), error.getDefaultMessage());
}The exception's BindingResult holds the outcome of the validation. getFieldErrors() returns one entry per broken rule, each with the field's name and the message from its annotation.
Return them with a 400
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}The map of field names to messages becomes the JSON body, and the status stays 400 Bad Request, because the client sent something wrong.
The response for a request with an empty pickup and zero passengers:
{"passengers":"Passengers must be at least 1","pickup":"Pickup location is required"}Every message came from the message attribute on RideRequest's constraint annotations. You know it worked when an invalid request returns this map instead of an empty 400: a client reading it knows exactly which two fields to fix.
One field can break two rules at once, such as a pickup that is both blank and too short. put keeps only the last message for each field, which is usually enough; if a client needs every message, collect a list per field instead. So the handler decides how much detail the client gets, and writing it once in a @RestControllerAdvice gives every endpoint the same error format.
← → move through sections and cards