When users send wrong data, we want to tell them clearly what is wrong. Formatting validation error responses helps users fix their mistakes easily.
Validation error response formatting in Spring Boot
Start learning this pattern below
Jump into concepts and practice - no test required
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.Map; import java.util.HashMap; @RestControllerAdvice public class ValidationExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ValidationErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) { ValidationErrorResponse errors = new ValidationErrorResponse(); ex.getBindingResult().getFieldErrors().forEach(error -> { String fieldName = error.getField(); String errorMessage = error.getDefaultMessage(); errors.addError(fieldName, errorMessage); }); return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); } } class ValidationErrorResponse { private Map<String, String> errors = new HashMap<>(); public void addError(String field, String message) { errors.put(field, message); } public Map<String, String> getErrors() { return errors; } }
This code uses @RestControllerAdvice to catch validation errors globally.
The MethodArgumentNotValidException contains details about which fields failed validation.
@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()) ); return ResponseEntity.badRequest().body(errors); }
public record ValidationError(String field, String message) {}
public record ValidationErrorResponse(List<ValidationError> errors) {}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {
List<ValidationError> errorList = ex.getBindingResult().getFieldErrors().stream()
.map(error -> new ValidationError(error.getField(), error.getDefaultMessage()))
.toList();
return ResponseEntity.badRequest().body(new ValidationErrorResponse(errorList));
}This Spring Boot app has a UserController that accepts a user with a name. The name must not be blank.
If the user sends an empty name, the ValidationExceptionHandler catches the error and returns a JSON with the field and message.
package com.example.demo; import jakarta.validation.constraints.NotBlank; import jakarta.validation.Valid; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } @RestController @RequestMapping("/users") class UserController { @PostMapping public String createUser(@RequestBody @Valid User user) { return "User created: " + user.name(); } } record User(@NotBlank(message = "Name must not be blank") String name) {} @RestControllerAdvice class ValidationExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); for (FieldError error : ex.getBindingResult().getFieldErrors()) { errors.put(error.getField(), error.getDefaultMessage()); } return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); } }
Always use @Valid on method parameters to trigger validation.
Customize error messages in your validation annotations for clearer feedback.
Return HTTP 400 (Bad Request) status for validation errors to follow web standards.
Validation error formatting helps users understand what went wrong.
Use @RestControllerAdvice and @ExceptionHandler to catch and format errors globally.
Return clear JSON with field names and error messages for easy frontend use.
Practice
Solution
Step 1: Understand validation error responses
Validation errors occur when user input does not meet rules. Formatting these errors helps users know what went wrong.Step 2: Identify the purpose of formatting
Clear error messages improve user experience by showing which fields have issues and why.Final Answer:
To provide clear error messages that help users understand input mistakes -> Option AQuick Check:
Validation error formatting = clear user messages [OK]
- Thinking error formatting speeds startup
- Assuming errors fix themselves automatically
- Confusing error formatting with package size
Solution
Step 1: Identify global exception handling annotation
@RestControllerAdvice is designed to handle exceptions across all controllers and format responses.Step 2: Confirm other annotations are not for global error handling
@Controller is for MVC controllers, @Service for business logic, @ComponentScan for scanning components, none handle exceptions globally.Final Answer:
@RestControllerAdvice -> Option AQuick Check:
Global error handler = @RestControllerAdvice [OK]
- Confusing @Controller with error handling
- Using @Service or @ComponentScan incorrectly
- Missing global exception handler annotation
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity> handleValidationErrors(MethodArgumentNotValidException ex) {
Map errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});
return ResponseEntity.badRequest().body(errors);
}Solution
Step 1: Analyze the error map creation
The code collects field errors and puts each field name as key and its error message as value in a Map.Step 2: Understand the response body
The Map is returned as JSON in the response body, so the client receives a JSON object with field-error pairs.Final Answer:
A JSON object with field names as keys and error messages as values -> Option CQuick Check:
Field-error map = JSON object with keys and messages [OK]
- Expecting plain text instead of JSON
- Thinking response is empty or only codes
- Confusing JSON array with JSON object
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity> handleErrors(MethodArgumentNotValidException ex) {
Map errors = new HashMap<>();
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.put(error.getDefaultMessage(), error.getField());
}
return ResponseEntity.badRequest().body(errors);
}Solution
Step 1: Check map key-value assignment
The code uses error.getDefaultMessage() as key and error.getField() as value, which reverses the intended field-to-message mapping.Step 2: Understand correct mapping
Field names should be keys and error messages should be values for clarity in JSON response.Final Answer:
The error message and field name are swapped when putting into the map -> Option BQuick Check:
Field as key, message as value is correct [OK]
- Swapping keys and values in error map
- Using wrong exception class
- Returning wrong HTTP status code
Solution
Step 1: Understand desired error response structure
The response should have timestamp, status code, and detailed field errors in JSON format.Step 2: Identify correct implementation method
Creating a custom error response class and populating it in a @RestControllerAdvice method allows structured JSON output with all required fields.Step 3: Exclude incorrect options
Returning plain strings or ModelAndView does not produce JSON with structured fields; throwing RuntimeException loses control over formatting.Final Answer:
Create a custom error response class with fields for timestamp, status, and a list of errors; populate it in a @RestControllerAdvice method handling MethodArgumentNotValidException -> Option DQuick Check:
Custom class + @RestControllerAdvice = structured JSON error [OK]
- Returning plain text instead of JSON
- Using @ControllerAdvice without JSON response
- Throwing exceptions instead of formatting response
