3.4 ResponseEntity & Content Negotiation
← Back to Chapter 3: REST API & Web Layer
ResponseEntity<T> represents the entire HTTP response: status code, headers, and body. It gives engineers total programmatic control over responses sent to clients.
1. HTTP Status Code Conventions
Using standard HTTP status codes correctly ensures clients can handle API outcomes predictably:
| Code Range | Category | Common REST Usages |
|---|---|---|
200 OK | Success | Standard response for successful GET, PUT, or PATCH. |
201 Created | Success | Successful POST creating a resource. Must include Location header. |
204 No Content | Success | Successful DELETE or action where no body needs to be returned. |
400 Bad Request | Client Error | Validation failures, malformed JSON, missing required headers. |
401 Unauthorized | Client Error | Missing or invalid authentication credentials (JWT missing/expired). |
403 Forbidden | Client Error | Authenticated user lacks permission/role for this resource. |
404 Not Found | Client Error | Target resource identifier does not exist. |
409 Conflict | Client Error | Duplicate unique key (e.g., email already registered). |
500 Internal Error | Server Error | Unhandled runtime exceptions, database failures. |
2. Constructing Responses with Fluent API
@RestController
@RequestMapping("/api/v1/accounts")
public class AccountController {
// 200 OK with custom headers
@GetMapping("/{id}")
public ResponseEntity<AccountDTO> getAccount(@PathVariable Long id) {
AccountDTO dto = accountService.findById(id);
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
.header("X-App-Version", "2.1.0")
.body(dto);
}
// 201 Created with Location URI
@PostMapping
public ResponseEntity<AccountDTO> createAccount(@Valid @RequestBody AccountRequest req) {
AccountDTO created = accountService.create(req);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.id())
.toUri();
return ResponseEntity.created(location).body(created);
}
// 204 No Content for deletion
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteAccount(@PathVariable Long id) {
accountService.delete(id);
return ResponseEntity.noContent().build();
}
}
3. Content Negotiation
Spring automatically inspects the client's Accept header to decide serialization format:
- Client sends
Accept: application/json➔ Jackson serializes to JSON. - Client sends
Accept: application/xml➔ Jackson XML module produces XML.
@GetMapping(value = "/{id}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public AccountDTO getNegotiated(@PathVariable Long id) {
return accountService.findById(id);
}