Получить доступ

Валидация и обработка ошибок

Bean Validation и @Valid/@Validated, превращение ошибок в ответ, @ControllerAdvice и единый контракт, ProblemDetail (RFC 7807), multipart и interceptors.

Bean Validation

Декларативную валидацию даёт Bean Validation (jakarta.validation): аннотации @NotNull, @NotBlank, @Size, @Email, @Pattern, @Min/@Max и собственные constraints прямо на полях DTO. В контроллере валидацию запускает @Valid на @RequestBody-параметре.

@Validated (на классе/параметре) добавляет группы валидации — разные правила для create и update. Вложенные объекты валидируются каскадно, если поле помечено @Valid. Важно: DTO входа отделяют от JPA-сущности — контракт API и модель хранения меняются по разным причинам и не должны быть связаны.

Как ошибка валидации становится ответом

Если @Valid @RequestBody не проходит проверки, Spring бросает MethodArgumentNotValidException, который по умолчанию превращается в 400 Bad Request. Детали (какие поля и почему не прошли) лежат в BindingResult и их собирают в ответ.

Поэтому валидацию НЕ пишут вручную в начале каждого метода — её декларируют аннотациями, а единый обработчик формирует понятный ответ со списком ошибок по полям.

record CreateUser(@NotBlank String name, @Email String email, @Min(18) int age) {}

@PostMapping
User create(@Valid @RequestBody CreateUser req) { /* при ошибке — 400 */ }

@ExceptionHandler и @ControllerAdvice

@ExceptionHandler обрабатывает исключение в пределах одного контроллера, а @RestControllerAdvice выносит обработку на всё приложение — это место для единого error-contract.

Хороший контракт ошибки стабилен и машиночитаем: статус, код ошибки, сообщение, ошибки по полям, correlation id для поиска в логах. Stacktrace и внутренние детали наружу не отдают — это и неудобно клиенту, и риск утечки информации.

@RestControllerAdvice
class ApiErrors {
    @ExceptionHandler(EntityNotFoundException.class)
    ProblemDetail notFound(EntityNotFoundException e) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
    }
}

ProblemDetail (RFC 7807)

Вместо самодельного ErrorDto современный стандарт ответа об ошибке — ProblemDetail по RFC 7807 (media type application/problem+json) с полями type, title, status, detail, instance и произвольными расширениями.

Spring 6 / Boot 3 поддерживают его из коробки (класс ProblemDetail, базовый ResponseEntityExceptionHandler уже отдаёт ошибки в этом формате). Единый стандартный формат удобнее клиентам и инструментам, чем зоопарк кастомных структур.

Multipart, локализация и перехватчики

Загрузку файлов (multipart/form-data) принимают через MultipartFile; для больших файлов настраивают лимиты и стриминг, чтобы не читать всё в память. MessageSource + LocaleResolver дают локализованные сообщения (locale, например, из Accept-Language).

Сквозную логику вокруг обработчика (correlation id, аудит, tenant context) вешают на HandlerInterceptor (работает внутри MVC, вокруг handler), а более низкоуровневую — на Servlet Filter (срабатывает раньше, до DispatcherServlet). Их разницу разбираем в уроке про сервлеты.

Как отвечать на собеседовании

Объясните декларативную валидацию (@Valid/@Validated + группы, каскад, отдельный DTO от entity) и путь ошибки: MethodArgumentNotValidException → 400 с field errors. Расскажите про @RestControllerAdvice как единую точку error-contract (status/code/message/field errors/correlation id, без stacktrace) и про современный ProblemDetail (RFC 7807) из Boot 3. Различите Interceptor (вокруг handler) и Filter (ниже, до DispatcherServlet).

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем