Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

CodeNameWhen
200OKData was read or changed
201CreatedA new object was created
204No ContentSuccess, but no response body — for example after deleting
400Bad RequestThe client sent invalid data
401UnauthorizedLogin required
403ForbiddenLogged in, but not allowed
404Not FoundThe object does not exist
500Internal Server ErrorA 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/2
HTTP/1.1 204

ResponseEntity or @ResponseStatus

TaskWhat to choose
The code is always the same@ResponseStatus or nothing (you get 200)
A header is needed, for example LocationResponseEntity
The code depends on a conditionResponseEntity, 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.

Thanks for reading!

Donate to the site — we work hard for you.