Ошибки REST — @Valid и @ControllerAdvice
Ошибки REST — @Valid и @ControllerAdvice
Рабочий REST на Spring Boot возвращает предсказуемые ошибки: JSON с кодом и описанием, а не HTML-страницу Whitelabel с трассировкой.
@ControllerAdvice — класс «рядом с контроллерами», который перехватывает исключения по всему приложению и формирует ответ. Один раз настроили — все @RestController получают единый формат.
Связано: Spring Security · JWT.
Словарь
| Термин | Объяснение |
|---|---|
| HTTP 400 Bad Request | Клиент прислал неверные данные (пустое поле, слишком длинный текст) |
| HTTP 404 Not Found | Ресурс с таким id отсутствует |
| HTTP 401 / 403 | Нет входа / нет прав (часто от Security до контроллера) |
| Bean Validation | Аннотации @NotBlank, @Size на полях DTO; проверка до бизнес-логики |
@Valid | «Проверь этот объект перед вызовом метода» |
| ProblemDetail | Стандарт RFC 7807: поля type, title, status, detail в JSON |
| DTO | Объект запроса/ответа API (CreateNoteRequest), отделён от entity БД |
POST /api/notes с пустым text — получите 400 и список полей. Затем исправьте тело и убедитесь, что приходит 201.Что получится
| Запрос | Ответ |
|---|---|
POST /api/notes с пустым text | 400 + список полей |
GET /api/notes/999 | 404 + ProblemDetail |
| Успешный POST | 201 + тело |
Зависимости
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
(Часто уже транзитивно из spring-boot-starter-web.)
DTO с валидацией
package com.example.notes;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record CreateNoteRequest(
@NotBlank(message = "text обязателен")
@Size(max = 500, message = "не длиннее 500 символов")
String text
) {}
Разбор аннотаций:
| Аннотация | Смысл |
|---|---|
@NotBlank | Строка не null, не пустая, не только пробелы |
@Size(max = 500) | Длина строки не больше 500 |
message = "..." | Текст попадёт в FieldError и в detail ответа |
Record в Spring Boot 3 — обычный DTO: валидация работает на компонентах конструктора.
Что произойдёт без @Valid: пустой text дойдёт до сервиса; клиент получит 500 или мусор в данных вместо понятного 400.
Простой сервис (в памяти)
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;
@Service
public class NoteService {
private final Map<Long, String> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong();
public record Note(long id, String text) {}
public Note create(String text) {
long id = seq.incrementAndGet();
store.put(id, text);
return new Note(id, text);
}
public Optional<Note> find(long id) {
return Optional.ofNullable(store.get(id)).map(t -> new Note(id, t));
}
}
Контроллер
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/notes")
public class NoteController {
private final NoteService service;
public NoteController(NoteService service) {
this.service = service;
}
@PostMapping
public ResponseEntity<NoteService.Note> create(@Valid @RequestBody CreateNoteRequest req) {
NoteService.Note saved = service.create(req.text());
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
@GetMapping("/{id}")
public NoteService.Note get(@PathVariable long id) {
return service.find(id).orElseThrow(() -> new NoteNotFoundException(id));
}
}
Разбор контроллера:
| Элемент | Роль |
|---|---|
@PostMapping | HTTP POST на /api/notes |
@Valid @RequestBody CreateNoteRequest | Разбор JSON + валидация; при ошибке метод не вызывается |
ResponseEntity.status(CREATED) | Статус 201 и тело созданной заметки |
orElseThrow(() -> new NoteNotFoundException(id)) | Нет id → исключение → advice вернёт 404 |
Доменное исключение
public class NoteNotFoundException extends RuntimeException {
public NoteNotFoundException(long id) {
super("Note not found: " + id);
}
}
@ControllerAdvice
package com.example.notes;
import org.springframework.http.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.util.stream.Collectors;
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
String details = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining("; "));
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.BAD_REQUEST, details);
problem.setTitle("Validation failed");
problem.setType(URI.create("about:blank"));
return problem;
}
@ExceptionHandler(NoteNotFoundException.class)
public ProblemDetail handleNotFound(NoteNotFoundException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
problem.setTitle("Not found");
return problem;
}
}
Разбор @RestControllerAdvice:
| Метод | Исключение | Ответ |
|---|---|---|
handleValidation | MethodArgumentNotValidException | 400, в detail — список полей |
handleNotFound | NoteNotFoundException | 404, сообщение из исключения |
ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, details) — фабрика Spring 6; сериализуется в JSON:
{
"type": "about:blank",
"title": "Validation failed",
"status": 400,
"detail": "text: text обязателен"
}
Фронтенд может показать detail пользователю или разобрать поля из строки; для сложных форм позже добавляют массив errors[] в кастомном теле.
Порядок обработки запроса:
POST /api/notes + JSON
→ Security (если включён)
→ DispatcherServlet
→ валидатор (@Valid) → 400 через advice
→ NoteController.create
→ 201 + тело
Проверка
curl -s -X POST http://localhost:8080/api/notes \
-H "Content-Type: application/json" \
-d '{"text":""}' | jq .
curl -s http://localhost:8080/api/notes/99999 | jq .
Ожидайте status: 400 и 404 без HTML Whitelabel.
Security + ошибки
При JWT неавторизованный запрос даёт 401 от Security до контроллера. Чтобы формат совпадал, можно добавить AuthenticationEntryPoint, возвращающий ProblemDetail — опционально для углубления.
Частые ошибки
| Симптом | Причина |
|---|---|
| 500 вместо 400 | Нет @Valid или нет starter-validation |
| Advice не срабатывает | Класс вне scan-пакета @SpringBootApplication |
Пустой detail | Нет message в аннотациях @NotBlank |
Что попробовать
@Email,@Minна других полях.@ControllerAdvice+ResponseEntityс кастомным телом{ code, errors[] }для legacy API.- Интеграционный тест
MockMvc—status().isBadRequest()иjsonPath("$.detail").
Дальше
JWT · JPA · JUnit / MockMvc
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Основы Java - устройство JDK/JVM, модель компиляции и базовые принципы платформонезависимого выполнения. Java — это объектно-ориентированный язык программирования общего назначения, который работает на принципах «напиши один раз, запускай в любом месте». Набор советов, правил, принципов и обычаев в разработке на этом языке. История Java — от проекта Green и Oak до OpenJDK, LTS-релизов и современной платформы (модули, records, виртуальные потоки). Библиотеки, фреймворки, инструменты сборки, тестирования, развёртывания и мониторинга. Что такое пакет и пакетная структура, как собираются проекты на Java. Справочник-шпаргалка по конфигурациям в Java — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Гайд по установке и настройке с написанием первой программы и её запуском. Практические примеры — консольные утилиты, композиция классов в мини-игре и первое Swing-приложение. Точки останова, пошаговое выполнение, панели Variables и Call Stack — практика отладки в IntelliJ IDEA. Кавычки, точки, запятые, скобки и прочие знаки препинания. Это полный справочник всех ключевых слов языка Java, включая основные, контекстные и зарезервированные слова.Основы языка Java
Что требуется знать перед началом изучения языка программирования Java
Рекомендации по разработке на Java
История языка Java
Экосистема Java-приложений
Структура и сборки Java-проектов
Справочник по конфигурациям в Java
Первая программа на Java
Простые приложения на Java
Отладка Java-кода в IDE
Синтаксис и пунктуация в Java
Ключевые слова в Java