Как запускается процесс валидации в Spring и что указать в параметрах метода контроллера
Что проверяют
Понимаете ли вы, кто и в какой момент запускает валидацию, а не только помните @Valid. Важны разница между телом запроса и простыми параметрами, типы исключений и то, что изменилось в Spring Framework 6.1. Следом обычно спрашивают про группы валидации и вложенные объекты.
Как отвечать
Нужна зависимость spring-boot-starter-validation: web-стартер Hibernate Validator не приносит, и без него аннотации просто игнорируются. В Spring Boot 3 пакет jakarta.validation, а не javax.validation.
- тело запроса:
@Valid @RequestBody UserDto dto. После десериализации черезHttpMessageConverterрезолвер аргумента вызывает валидатор черезWebDataBinder, при ошибке летитMethodArgumentNotValidExceptionи ответ 400; - форма и query-объект:
@Valid @ModelAttribute, при ошибкеBindException. Если сразу после параметра объявитьBindingResult, исключения не будет и ошибки разбираете сами; - простые параметры:
@PathVariable @Min(1) Long idили@RequestParam @NotBlank String q. С версии 6.1 Spring MVC проверяет их встроенной валидацией методов и бросаетHandlerMethodValidationException. До этого требовался@Validatedна классе и AOP-прокси сConstraintViolationException; - группы:
@Validated(OnCreate.class)вместо@Valid, стандартная аннотация группы не принимает; - вложенность:
@Validна поле или на элементе коллекцииList<@Valid ItemDto>, иначе проверка вглубь не пойдёт.
Ответ с ошибками собирают в @RestControllerAdvice через @ExceptionHandler или отдают как ProblemDetail.
Где спотыкаются
Забывают стартер валидации или импортируют javax.validation в проекте на Boot 3. Если старый пакет попал в зависимости транзитивно, код компилируется, но ни одна проверка не срабатывает.
Ставят @NotNull на строку и считают, что пустая строка отсеется. Для этого нужен @NotBlank.
Оставляют @Validated на классе контроллера в Spring 6.1 и новее. Встроенная проверка тогда отключается, параметры проверяет AOP-прокси, и вместо HandlerMethodValidationException прилетает ConstraintViolationException, которое без своего обработчика превращается в 500.