Ответы и коды статуса
Каждый ответ HTTP начинается с кода статуса — трёхзначного числа, по которому клиент понимает, чем закончился запрос. По умолчанию Spring отвечает 200 OK. Но для созданной статьи правильнее 201 Created, а для удалённой — 204 No Content. Код задают через класс ResponseEntity или аннотацию @ResponseStatus.
Основные коды
| Код | Название | Когда |
|---|---|---|
200 | OK | Данные получены или изменены |
201 | Created | Создан новый объект |
204 | No Content | Успех, но тела ответа нет — например, после удаления |
400 | Bad Request | Клиент прислал неверные данные |
401 | Unauthorized | Нужен вход |
403 | Forbidden | Вход есть, но прав не хватает |
404 | Not Found | Объекта нет |
500 | Internal Server Error | Ошибка в коде сервера |
Создание: 201 и адрес новой статьи
// src/main/java/com/example/blog/PostController.java
@PostMapping
public ResponseEntity<Post> create(@RequestBody NewPostRequest request) {
Post post = postService.create(request);
return ResponseEntity.created(URI.create("/api/posts/" + post.id())).body(post);
}ResponseEntity — это весь ответ целиком: код, заголовки и тело. created(...) ставит код 201 и заголовок Location с адресом новой статьи, body(...) кладёт саму статью.
curl -i -X POST http://localhost:8080/api/posts \
-H "Content-Type: application/json" \
-d '{"title":"Привет, Spring","body":"Первая статья","author":"anna"}'HTTP/1.1 201
Location: /api/posts/1
Content-Type: application/json
{"id":1,"title":"Привет, Spring","body":"Первая статья","author":"anna","createdAt":"2026-10-04T08:50:07.494425300Z"}Изменение
// src/main/java/com/example/blog/PostController.java
@PutMapping("/{id}")
public Post update(@PathVariable Long id, @RequestBody NewPostRequest request) {
return postService.update(id, request);
}// src/main/java/com/example/blog/PostService.java
public Post update(Long id, NewPostRequest request) {
Post old = findById(id);
return repository.save(new Post(id, request.title(), request.body(), request.author(), old.createdAt()));
}PUT заменяет статью целиком, поэтому принимает тот же NewPostRequest. Дата создания берётся из старой версии. Ответ — обычный 200 с обновлённой статьёй.
Удаление: 204 без тела
// src/main/java/com/example/blog/PostController.java
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
postService.delete(id);
}// src/main/java/com/example/blog/PostService.java
public void delete(Long id) {
findById(id);
repository.deleteById(id);
}Когда возвращать нечего, удобнее @ResponseStatus: метод возвращает void, а код задан аннотацией. Вызов findById в сервисе выбросит PostNotFoundException, если статьи нет.
curl -i -X DELETE http://localhost:8080/api/posts/2HTTP/1.1 204ResponseEntity или @ResponseStatus
| Задача | Что выбрать |
|---|---|
| Код всегда один и тот же | @ResponseStatus или ничего (будет 200) |
Нужен заголовок, например Location | ResponseEntity |
| Код зависит от условия | ResponseEntity, например ResponseEntity.notFound().build() |
Частая ошибка
Ответ 200 на создание формально работает, но клиенты API ждут 201 и заголовок Location. Ещё хуже возвращать 200 с текстом ошибки внутри JSON: программа-клиент проверяет код статуса и решит, что всё прошло успешно. Код статуса должен говорить правду о результате.