Handling Validation Errors
Handling Validation Errors
In the previous lesson you learned how to declare constraints on your request-body objects and activate them with @Valid. But what actually happens when a constraint is violated? Spring throws a MethodArgumentNotValidException, and unless you intercept it you get a raw 400 response with a stack-trace-heavy JSON payload that leaks implementation details and is useless to API consumers. This lesson teaches you to read that exception, extract the per-field messages, and return a clean, structured error response that your clients can actually act on.
What Spring Throws — and Why
When Spring MVC binds a @RequestBody and runs the @Valid check, Bean Validation populates a BindingResult internally. If any constraint fails, Spring immediately raises MethodArgumentNotValidException — a subclass of BindException — before your controller method body even runs. The exception carries the full BindingResult, which is a container of FieldError and ObjectError objects.
FieldError refers to a single property (e.g. email is blank). An ObjectError (also called a global error) refers to the whole object — typically produced by a class-level constraint. Both are accessible through BindingResult.
Accessing the BindingResult Directly
The simplest way to handle errors without a separate exception handler is to add a BindingResult parameter immediately after the @Valid parameter in your controller method signature. Spring then does not throw — it populates the result and lets your method decide what to do.
This approach works, but it has a significant drawback: you must duplicate the error-extraction logic in every controller method that receives a validated body. For anything beyond a trivial project, centralising that logic is far better.
BindingResult parameter, Spring suppresses the exception. Your @ExceptionHandler(MethodArgumentNotValidException.class) will never fire for that endpoint. Pick one strategy per method.
Extracting Errors from MethodArgumentNotValidException
The centralised approach — covered in depth in lessons 6 and 7 — uses @ExceptionHandler. For now, understand the API you will use inside that handler. MethodArgumentNotValidException exposes the same BindingResult:
The merge function in Collectors.toMap handles the case where a single field violates more than one constraint simultaneously — for example a password field that is both too short and lacks an uppercase letter.
Designing a Helpful Error Payload
A well-designed error response answers three questions for the API consumer: What went wrong? Which field caused it? What should I send instead? A flat Map<String, String> of field-to-message pairs is the minimum viable structure. A richer record adds an HTTP status, a timestamp, and a machine-readable error code:
A response from this record looks like:
MessageSource. Override them only when the default is genuinely confusing in your domain — over-customising creates maintenance overhead.
Localising Error Messages
Bean Validation looks up constraint messages through a MessageSource. Spring Boot auto-configures one that reads from src/main/resources/ValidationMessages.properties (and locale-specific variants like ValidationMessages_ar.properties). Override any default message by adding its key:
You can also write inline messages directly on the annotation, which take priority:
message strings, {min}, {max}, and {value} are replaced with the constraint's attribute values at runtime. This avoids magic numbers scattered across error strings.
Handling Constraint Violations on Path Variables and Query Params
When you annotate method parameters directly — rather than a request body — Bean Validation throws ConstraintViolationException (not MethodArgumentNotValidException). You must handle both:
This is also why your controller class must be annotated with @Validated (not just @Valid on the parameter) when validating path variables and request params — Spring needs the class-level annotation to apply the AOP proxy that intercepts method calls.
Returning the Right HTTP Status
Validation failures are the client's fault, so the correct status is always in the 4xx range:
- 400 Bad Request — the request body or parameters violate constraints. This is the standard choice for validation errors.
- 422 Unprocessable Entity — semantically well-formed but logically invalid (e.g. a start date after an end date). Some teams prefer 422 specifically for business-rule violations to distinguish them from format errors.
- 404 Not Found — do not return this for a missing required field; reserve it for "the resource you asked about does not exist".
Summary
When Bean Validation fails, Spring throws either MethodArgumentNotValidException (request body) or ConstraintViolationException (method parameters). Both carry a list of errors you can iterate and convert into field-to-message maps. Return a structured JSON payload with a 400 status, field names, and the constraint messages — either inline per method using BindingResult, or (better) centrally in an exception handler. The next lesson shows you how to write that handler with @ExceptionHandler.