Rejecting bad input at the edge keeps invalid data out of your services and database.
Add spring-boot-starter-validation (Hibernate Validator). Then put constraints on DTO fields or record components: @NotBlank, @NotNull, @Size(max = 100), @Email, @Min, @Max, @Positive, @Past, @Pattern(regexp = ...). In Boot 3 they live in the jakarta.validation.constraints package (not javax).
Trigger validation by annotating the controller parameter with @Valid (or @Validated): create(@Valid @RequestBody CreateUser req). If a constraint fails, the method isn't called and Spring throws MethodArgumentNotValidException, which becomes a 400 Bad Request. Customise the response body with an exception handler (next lesson).
Nested objects are validated only if the field itself is annotated @Valid. For @PathVariable and @RequestParam constraints (@Min(1) @PathVariable long id), Spring MVC 6.1+ applies method validation automatically when constraints are present.
Write custom constraints when the built-ins aren't enough: an annotation plus a ConstraintValidator class. Cross-field rules (end after start) can be a constraint on the class.
Validation in the web layer complements, but doesn't replace, database constraints and business-rule checks in services. You can also validate service methods with @Validated on the class, and @ConfigurationProperties classes the same way.
Example
record CreateUser(
@NotBlank @Size(max = 50) String name,
@NotBlank @Email String email,
@Min(13) int age,
@Valid @NotNull Address address) {}
record Address(@NotBlank String city, @Pattern(regexp = "\\d{4}") String zip) {}
@RestController
@RequestMapping("/api/users")
class UserController {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
CreateUser create(@Valid @RequestBody CreateUser req) {
return req; // only reached when everything is valid
}
@GetMapping("/{id}")
String get(@PathVariable @Min(1) long id) { return "user " + id; }
}
// POST {"name":"","email":"x","age":10,"address":{"city":"Yerevan","zip":"12"}}
// -> 400, errors on name, email, age, address.zip
// GET /api/users/0 -> 400 (method validation)Remember
- Constraints like
@NotBlank,@Email,@Sizelive injakarta.validation @Validon the parameter triggers validation; failures give 400- Cascade into nested objects with
@Validon the field - Validation at the edge complements database constraints
Watch out for
- Forgetting @Valid on the @RequestBody parameter, so all the constraint annotations are silently ignored.