Responses and status codes
Every HTTP response starts with a status code — a three-digit number that tells the client how the request ended. By default Spring answers 200 OK. But a created post deserves 201 Created, and a deleted one 204 No Content. The code is set with the ResponseEntity class or the @ResponseStatus annotation.
The main codes
| Code | Name | When |
|---|---|---|
200 | OK | Data was read or changed |
201 | Created | A new object was created |
204 | No Content | Success, but no response body — for example after deleting |
400 | Bad Request | The client sent invalid data |
401 | Unauthorized | Login required |
403 | Forbidden | Logged in, but not allowed |
404 | Not Found | The object does not exist |
500 | Internal Server Error | A bug in the server code |
Creating: 201 and the new post's address
// 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 is the whole response: code, headers and body. created(...) sets code 201 and a Location header with the new post's address, body(...) puts in the post itself.
curl -i -X POST http://localhost:8080/api/posts \
-H "Content-Type: application/json" \
-d '{"title":"Hello, Spring","body":"First post","author":"anna"}'HTTP/1.1 201
Location: /api/posts/1
Content-Type: application/json
{"id":1,"title":"Hello, Spring","body":"First post","author":"anna","createdAt":"2026-10-04T08:50:07.494425300Z"}Updating
// 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 replaces the whole post, so it accepts the same NewPostRequest. The creation date is taken from the old version. The response is a normal 200 with the updated post.
Deleting: 204 without a body
// 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);
}When there is nothing to return, @ResponseStatus is handier: the method returns void, and the annotation sets the code. The findById call in the service throws PostNotFoundException if the post does not exist.
curl -i -X DELETE http://localhost:8080/api/posts/2HTTP/1.1 204ResponseEntity or @ResponseStatus
| Task | What to choose |
|---|---|
| The code is always the same | @ResponseStatus or nothing (you get 200) |
A header is needed, for example Location | ResponseEntity |
| The code depends on a condition | ResponseEntity, for example ResponseEntity.notFound().build() |
Common mistake
Answering 200 to a create request technically works, but API clients expect 201 and a Location header. It is even worse to return 200 with an error message inside the JSON: the client program checks the status code and decides everything went fine. The status code must tell the truth about the result.