HTTP-методы и статусы: ResponseEntity и @ResponseStatus
Хорошее REST API говорит статусами: 200 — успех, 201 — создано, 204 — без содержимого, 404 — не найдено. Spring даёт два инструмента управления ими.
Суть: @ResponseStatus фиксирует статус метода декларативно, а ResponseEntity даёт полный программный контроль над статусом, заголовками и телом.
HTTP-статус — это первое, что читает клиент. Если ваш API на любую операцию возвращает 200, клиент не сможет отличить успех создания от ошибки. Грамотные статусы делают API предсказуемым и самодокументируемым.
Семантика основных кодов
| Код | Значение | Когда возвращать |
|---|---|---|
| 200 OK | Успех | Успешный GET, PUT |
| 201 Created | Создано | Успешный POST |
| 204 No Content | Без тела | Успешный DELETE |
| 400 Bad Request | Ошибка клиента | Невалидные данные |
| 404 Not Found | Не найдено | Ресурс отсутствует |
ResponseEntity — полный контроль
@PostMapping("/api/users")
public ResponseEntity<User> create(@RequestBody CreateUserRequest req) {
User saved = service.create(req);
return ResponseEntity
.status(HttpStatus.CREATED) // 201
.header("Location", "/api/users/" + saved.getId())
.body(saved);
}
@GetMapping("/{id}")
public ResponseEntity<User> byId(@PathVariable Long id) {
return service.findOptional(id)
.map(ResponseEntity::ok) // 200 + тело
.orElse(ResponseEntity.notFound().build()); // 404
}
ResponseEntity — обёртка над всем ответом: статус, заголовки, тело. Это самый гибкий способ, особенно когда статус зависит от условия (нашли — 200, нет — 404).
@ResponseStatus — декларативно
@PostMapping("/api/users")
@ResponseStatus(HttpStatus.CREATED) // всегда 201
public User create(@RequestBody CreateUserRequest req) {
return service.create(req);
}
Если статус фиксированный, @ResponseStatus короче и читается лучше.
Как работает под капотом
Когда метод возвращает обычный объект, Spring оборачивает его в ответ со статусом 200 (или тем, что указан в @ResponseStatus). Когда возвращается ResponseEntity, Spring берёт статус, заголовки и тело прямо из него, не добавляя своих умолчаний.
Метод контроллера
|
┌────┴─────────────────────────┐
v v
обычный объект ResponseEntity
| |
статус = 200 статус/заголовки/тело
(или @ResponseStatus) берутся из объекта
| |
└──────────────┬───────────────┘
v
HTTP-ответ клиенту
Частые ошибки
- POST возвращает 200 вместо 201. Создание ресурса должно отвечать 201 Created.
- 404 как 200 с пустым телом. Если ресурса нет, верните 404, а не «успех» с null.
- Ошибки как 200. Бизнес-ошибки должны отражаться в статусах 4xx, а не маскироваться под успех.
Best practices
- Используйте
@ResponseStatusдля фиксированного статуса,ResponseEntity— когда статус зависит от логики. - На создание возвращайте 201 и заголовок
Locationс адресом нового ресурса. - На удаление — 204 без тела.
Итог: статусы — язык REST. ResponseEntity даёт полный контроль, @ResponseStatus — лаконичность для фиксированных кодов. Возвращайте честные статусы — это основа удобного API.
Закрепим главное
Грамотные HTTP-статусы — признак зрелого API. Клиентский код полагается на них: фронтенд по статусу 201 понимает, что создание прошло, по 404 показывает «не найдено», по 400 — выводит ошибки формы. Если же всё возвращает 200, клиенту приходится разбирать тело ответа вручную, и логика расползается. Поэтому относитесь к статусу как к части контракта, а не как к мелочи.
Запомните практическое правило выбора инструмента. Когда статус метода фиксирован и не зависит от данных, лаконичнее @ResponseStatus — он сразу виден над методом. Когда же ответ ветвится (нашли ресурс — 200, не нашли — 404, создали — 201 с заголовком Location), нужен ResponseEntity, дающий полный программный контроль. Не пытайтесь выразить ветвление через @ResponseStatus — получится неуклюже. И всегда возвращайте заголовок Location при создании ресурса: это часть соглашений REST, по которой клиент узнаёт адрес новой сущности.