# 3. Validation errors that say something [← 2. Choosing a mechanism](02-choosing-a-mechanism.md) · [Index](../README.md) · Next: [4. Types and message codes →](04-i18n-and-types.md) Two exceptions, depending on where the constraint is: | Constraint on | Exception | Stock `detail` | |---|---|---| | a `@Valid @RequestBody` object | `MethodArgumentNotValidException` | `Invalid request content.` | | a parameter directly (`@RequestParam @Max(100) int limit`) | `HandlerMethodValidationException` | `Validation failure` | The second is Spring Framework 6.1's built-in method validation: a constraint annotation on a controller parameter is enough, no `@Validated` on the class. ## The stock body tells the client nothing From [`matrix-boot-flag.txt`](output/matrix-boot-flag.txt) - three violations in the request, none in the response: ``` HTTP 400 Content-Type: application/problem+json {"detail":"Invalid request content.","instance":"/orders","status":400,"title":"Bad Request"} ``` ## Adding the violations Override the protected hook, keep the body the base class built, add an extension member, and hand it back to `handleExceptionInternal` so headers and status stay right ([`GlobalExceptionHandler`](../src/main/java/com/ankurm/problems/advice/GlobalExceptionHandler.java)): ```java @Override protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { ProblemDetail pd = ex.getBody(); pd.setDetail("The request body has " + ex.getErrorCount() + " invalid field(s)"); pd.setProperty("errors", ex.getFieldErrors().stream() .map(e -> Map.of("pointer", "#/" + e.getField().replace('.', '/'), "detail", String.valueOf(e.getDefaultMessage()))) .toList()); return handleExceptionInternal(ex, pd, headers, status, request); } ``` The `{"pointer","detail"}` shape is not invented: it is the example RFC 9457 itself uses in section 3. From [`matrix-advice.txt`](output/matrix-advice.txt): ``` {"detail":"The request body has 3 invalid field(s)","instance":"/orders","status":400,"title":"Bad Request","errors":[{"pointer":"#/quantity","detail":"must be greater than or equal to 1"},{"pointer":"#/customerEmail","detail":"must be a well-formed email address"},{"pointer":"#/sku","detail":"must not be blank"}]} ``` ## Do not assert on the order The order in that line is not declaration order, and it differed between runs of the same request. The contract test asserts membership, not position. ## Nested fields `FieldError.getField()` uses dots for nesting (`address.city`) and brackets for indexes (`lines[0].sku`). The `replace('.', '/')` here handles the first; if your API has lists of objects, convert `[n]` to `/n` as well so the pointer is a valid JSON Pointer.