Intermediate · Harden

Build a Stable Validation & Error Contract

Separate binding, constraint violations, business conflicts, and unexpected failures without leaking internals.

Primary source: Spring Framework 6.2: MethodValidationPostProcessor

40 min

Lesson 1 of 3

1d streak

The Architectural Question

Ad-hoc error JSON per endpoint leaves clients writing bespoke parsers, and genuine regressions hide inside the format churn.

What this guide proves

  • Use Jakarta Validation for deterministic request-shape rules
  • Represent errors with a consistent `ProblemDetail` extension
  • Map domain failures explicitly instead of catching `Exception`

How Jakarta Bean Validation intercepts incoming payloads

One validation boundary, taught once

This lesson starts with the MVC binding trigger, then owns reusable constraints, validation groups, and the stable public error contract. Keep transport-shape checks here and state-dependent business decisions in the application or domain layer.

Jakarta Bean Validation (implemented by Hibernate Validator in Spring Boot) provides declarative constraint enforcement on Java objects. Annotating a controller @RequestBody parameter with @Valid causes Spring’s RequestResponseBodyMethodProcessor to trigger validator inspection immediately after deserializing JSON.

  1. 01Jackson deserializes the HTTP request JSON body into a Java Record or POJO.
  2. 02Spring MVC detects the @Valid or @Validated annotation on the method parameter.
  3. 03ValidatorFactory executes registered ConstraintValidators for every annotated field.
  4. 04If any constraint fails, Spring constructs a BindingResult and immediately throws MethodArgumentNotValidException.
  5. 05The controller method body never executes, protecting domain services from invalid state.

Architectural Boundary · Do Not Confuse

Concept A

@Valid (Standard Jakarta)

Concept B

@Validated (Spring Specific)

@Valid is the standard Jakarta validation annotation that triggers cascading validation on nested objects and controller bodies. @Validated is a Spring-specific annotation that enables method-level parameter validation (on @Service methods) and supports Validation Groups.

Annotating wire contracts with declarative constraints

CreateCustomerRequest.java
package com.example.customer.dto;

import jakarta.validation.constraints.*;

public record CreateCustomerRequest(
    @NotBlank(message = "Customer name is required") 
    @Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters") 
    String name,

    @NotBlank(message = "Email is required") 
    @Email(message = "Email must be formatted properly") 
    String email,

    @NotNull(message = "Credit limit is required") 
    @PositiveOrZero(message = "Credit limit cannot be negative") 
    Double creditLimit
) {}
CustomerController.java
package com.example.customer.api;

import com.example.customer.dto.CreateCustomerRequest;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;

@RestController
@RequestMapping("/api/customers")
public class CustomerController {

    @PostMapping
    public ResponseEntity<Void> create(@Valid @RequestBody CreateCustomerRequest request) {
        // Method body executes only if request passes all validations
        URI location = URI.create("/api/customers/42");
        return ResponseEntity.created(location).build();
    }
}

Break it deliberately: observing MethodArgumentNotValidException

Terminal / Diagnostic Output
curl -i -X POST http://localhost:8080/api/customers \
  -H "Content-Type: application/json" \
  -d '{"name": "", "email": "invalid-email", "creditLimit": -50}'

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "Validation failed for argument [0] in public ResponseEntity com.example.customer.api.CustomerController.create(...)",
  "errors": [
    {"field": "name", "message": "Customer name is required"},
    {"field": "email", "message": "Email must be formatted properly"},
    {"field": "creditLimit", "message": "Credit limit cannot be negative"}
  ]
}

Active Recall Checkpoint

What happens if you omit the @Valid annotation on a controller @RequestBody argument?

Hint: Does Hibernate Validator inspect the object automatically without the annotation?

Show answer & explanation

Spring skips validation entirely. The deserialized object—including null or invalid fields—passes straight into your controller and service logic, risking NullPointerExceptions or corrupted database rows.

Validation rules in API design

Use it when

Apply declarative Jakarta validation annotations on every input DTO representing external HTTP, RPC, or messaging payloads.

Reconsider when

Avoid placing complex stateful business rules (e.g. checking account balance or inventory levels) in Jakarta annotations; keep those in Domain Services.

Synthesis & Production Verification

Production Acceptance Criteria

  • Return identical error envelopes for invalid-email, 404, and duplicate-resource cases
  • Bind field errors to JSON pointers a client can highlight
  • Prove no stack traces or SQL fragments appear in any payload

Architectural & Technical Review

Core Interview Questions

Q01How does @Valid trigger validation in a Spring controller?Show answer

Spring detects @Valid on the @RequestBody parameter and invokes ValidatorFactory, throwing MethodArgumentNotValidException if constraints fail.

Q02What HTTP status and payload structure should validation failures produce?Show answer

An HTTP 400 Bad Request with an RFC 9457 ProblemDetail document detailing each invalid field and constraint violation message.

Q03What is the distinction between @Valid and @Validated?Show answer

@Valid is standard Jakarta validation for request bodies and nested objects; @Validated is Spring-specific for service method validation and groups.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Omitting @Valid from @RequestBody parameters — Without @Valid, Spring skips validation entirely, allowing malformed or null values to propagate into domain services.
  • Pitfall: Writing manual if-null validation checks inside controller methods — Manual checks duplicate boilerplate and produce inconsistent error responses compared to declarative Jakarta validation.

Primary Documentation & Specifications