Appearance
ResponseEntity and HTTP Status Codes
Chapter 5 mapped requests to controller methods and used ResponseEntity briefly. Before the next chapter turns exceptions into proper error responses, this chapter covers responses in depth: how to build one with ResponseEntity, and which status code to return in which situation, category by category (1xx to 5xx).
An HTTP response has three parts:
- Status code: the result, such as
200 OKor500 Internal Server Error. - Headers: extra information (optional).
- Body: the data you send back.
ResponseEntity
ResponseEntity<T> represents the whole response. T is the type of the body.
java
@GetMapping("/get-user")
public ResponseEntity<String> getUser() {
return ResponseEntity.ok("My response body object can go here");
}To set the status and headers yourself:
java
@GetMapping("/get-user")
public ResponseEntity<String> getUser() {
HttpHeaders headers = new HttpHeaders();
headers.add("My-Header1", "SomeValue1");
headers.add("My-Header2", "SomeValue2");
return ResponseEntity.status(HttpStatus.OK)
.headers(headers)
.body("My response body object can go here");
}This is the builder pattern: status(...) and headers(...) return a builder, and body(...) returns the finished ResponseEntity, so body always comes last. When there is no body, finish with build() and use Void as the type:
java
@GetMapping("/get-user")
public ResponseEntity<Void> getUser() {
return ResponseEntity.status(HttpStatus.OK).headers(headers).build();
}The default: 200 OK
If a @RestController method returns a plain object, the status is 200 OK:
java
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/get-user")
public User getUser() {
return new User("XYZ", 28); // 200 OK, User as JSON in the body
}
}This works without @ResponseBody because @RestController adds it to every method. With @Controller instead, returning "XYZ" would make Spring look for a view named XYZ, and the request fails with 404 (chapter 5).
Status codes
The first digit gives the category.
1xx: Informational
An interim answer before the final one.
| Code | Name | Used when |
|---|---|---|
| 100 | Continue | Before sending a large body (say a 1 MB file upload), the client sends only the headers, including Expect: 100-continue. The server checks what it can (authentication, authorization, content type, length) and, if all is fine, answers 100 Continue. The client then sends the body on the same request, and the server processes it. |
2xx: Success
The request was received and processed.
| Code | Name | Used mostly with | Meaning |
|---|---|---|---|
| 200 | OK | GET, POST | Success, and the response has a body. |
| 201 | Created | POST | Success, and a new resource was created. |
| 202 | Accepted | POST | Accepted, but processing is not finished yet, for example a batch export or import. |
| 204 | No Content | DELETE | Success, and there is no body. |
| 206 | Partial Content | GET | The server returns only part of a resource because the client asked for a byte range (a Range header), as when resuming a download. |
You will see 206 used for bulk operations that partly succeeded (95 of 100 users added). That is not what the standard defines it for: for mixed results, return 200 (or 207 Multi-Status) with a per-item result in the body.
200 is also the right answer to a repeated, idempotent POST: if the same "create" request arrives twice and the second one creates nothing new, return 200 with the existing resource, and keep 201 for the call that really created it.
3xx: Redirection
The client must do something more to complete the request.
| Code | Name | Meaning |
|---|---|---|
| 301 | Moved Permanently | The API moved for good, for example when migrating from a legacy API; clients should use the new URI from the Location header. Clients may change the method on redirect (a POST may be retried as GET). |
| 308 | Permanent Redirect | Same as 301, but the method must not change: if the old call was POST, the new one is POST too. |
| 304 | Not Modified | The client cached an earlier GET response along with its Last-Modified time. On the next GET it sends that time in If-Modified-Since; if the resource has not changed, the server answers 304 with no body and the client uses its cache. |
Try a 301. An old endpoint that has moved:
java
@GetMapping("/old-get-user")
public ResponseEntity<Void> oldGetUser() {
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY)
.header("Location", "/api/new-get-user")
.build();
}
@GetMapping("/new-get-user")
public ResponseEntity<String> newGetUser() {
return ResponseEntity.status(HttpStatus.OK).body("success");
}A client that does not follow redirects sees the bare answer:
http
HTTP/1.1 301
Location: /api/new-get-user
Content-Length: 0A client that does (browsers, Postman by default, curl -L) reads Location, calls the new URI, and ends with 200 and success, as if it had called the new API directly. In Postman, turning off Settings → Automatically follow redirects shows the raw 301. That is the "additional action" of 3xx: the client, not the server, makes the second call.
304 is for caching GETs. If a PATCH changes nothing (the name is already the same in the database), answer 200 or 204, not 304.
4xx: Client errors
The client must send a correct request.
| Code | Name | Example |
|---|---|---|
| 400 | Bad Request | The client did not send the details needed to process the request. |
| 401 | Unauthorized | The API needs authentication (a Bearer token, Basic authentication, ...) and the client sent none. |
| 403 | Forbidden | The client is authenticated but not allowed: only ADMIN may do this operation and a non-admin called it. |
| 404 | Not Found | The resource is not there: GET user 123, but no such ID exists in the database. |
| 405 | Method Not Allowed | Calling a GET API with the POST method. In Spring Boot the DispatcherServlet rejects it before any controller runs, and adds an Allow header listing the methods that work (Allow: GET). |
| 409 | Conflict | The request conflicts with the resource's current state. For example, a PATCH on user 123 is still in progress and an identical one arrives: the server keeps a lock per resource (often in a cache, with a short TTL so it frees itself) and answers the second with 409. Common with POST, PATCH and DELETE. |
| 422 | Unprocessable Entity | The request is well formed but fails a business rule: users from a country you do not support yet cannot open an account. |
| 429 | Too Many Requests | Rate limiting: each user may make 10 calls a minute, and user 12345 makes an 11th. |
5xx: Server errors
The client's request was valid, but something failed on the server.
| Code | Name | Meaning |
|---|---|---|
| 500 | Internal Server Error | Generic error when no more specific code fits. |
| 501 | Not Implemented | The server cannot fulfil this request, for example the API is still being built. |
| 502 | Bad Gateway | A server acting as a proxy got an invalid response from upstream. Your app runs behind Nginx, and Nginx cannot reach it (say, because of a wrong port): Nginx returns 502. |
Next: Status codes say what happened; most error codes come from exceptions. Chapter 22 follows an exception thrown by a controller through Spring, and shows how to turn it into the right status code and a useful error body.
Check yourself
- What are the three parts of an HTTP response?
- Why must
.body(...)come last when building aResponseEntity? What do you call instead when there is no body? - What status does a
@RestControllerreturn by default? - A client uploads a large file and wants to know first whether the server will accept it. Which header and status code are involved?
- Which code tells the client "done, nothing to return"? And "accepted, still working on it"?
- What is the difference between 301 and 308?
- What is the difference between 401 and 403?
- The request is valid JSON but the country is not supported. Which code?
- Your app is down behind Nginx and Nginx cannot reach it. Which code does the client see?
Answers
- Status code, headers and body.
statusandheadersreturn a builder;bodyreturns the finishedResponseEntity. Usebuild()when there is no body.200 OK.- The client sends
Expect: 100-continue; the server answers100 Continue, then the client sends the body. 204 No Content.202 Accepted.- Both are permanent redirects; 308 forbids changing the HTTP method, 301 allows it.
- 401: not authenticated. 403: authenticated but not allowed.
422 Unprocessable Entity.502 Bad Gateway.