ResponseEntity
Professional REST API responses — controlling status, headers and body, 200/201/204/400/404/500, Location headers, cookies, file downloads, response wrappers and ResponseEntity vs @ResponseStatus.
#Introduction
If there is one class that separates beginner Spring developers from experienced backend developers, it is ResponseEntity — almost every enterprise Spring Boot project uses it.
Interviewers frequently ask: What is ResponseEntity? Why not return an object directly? When should we return 200, 201, 204, 400, 404 or 500? How do we return custom headers or file downloads? What's the difference between @ResponseStatus and ResponseEntity?
#What is ResponseEntity?
ResponseEntity represents the entire HTTP response. Unlike returning just an object, it lets you control:
- HTTP response
- Status code
- Headers
- Body
#Why Do We Need ResponseEntity?
Without it:
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return service.findById(id);
}Spring returns:
HTTP/1.1 200 OK
Content-Type: application/json
{"id":1,"name":"Rahul"}You can't easily change the status code or add headers. With ResponseEntity:
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = service.findById(id);
return ResponseEntity.ok(user);
}Now you have complete control.
#Anatomy of ResponseEntity
ResponseEntity<User> = status + headers + body (User).
#Common Status Codes
200 OK
The most common response:
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = service.findById(id);
return ResponseEntity.ok(user);
}HTTP/1.1 200 OK
Content-Type: application/json
{"id":1,"name":"Rahul"}201 Created
Whenever a new resource is created:
@PostMapping
public ResponseEntity<User> save(@RequestBody UserRequest dto) {
User saved = service.save(dto);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(saved);
}Better: REST recommends returning the location of the newly created resource:
@PostMapping
public ResponseEntity<User> save(@RequestBody UserRequest dto) {
User saved = service.save(dto);
URI location = URI.create("/users/" + saved.getId());
return ResponseEntity
.created(location)
.body(saved);
}HTTP/1.1 201 Created
Location: /users/101204 No Content
Useful after DELETE:
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
service.delete(id);
return ResponseEntity.noContent().build();
}HTTP/1.1 204 No ContentNo body.
404 Not Found
@GetMapping("/{id}")
public ResponseEntity<User> get(@PathVariable Long id) {
User user = service.find(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}Different responses based on business logic. (With an Optional, ResponseEntity.of(optionalUser) does the same in one line.)
400 Bad Request
@PostMapping
public ResponseEntity<String> save(@RequestBody UserRequest dto) {
if (dto.getAge() < 18) {
return ResponseEntity
.badRequest()
.body("Age must be at least 18");
}
return ResponseEntity.ok("Saved");
}500 Internal Server Error
catch (Exception ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Unexpected Error");
}In enterprise applications this is usually handled globally using @ControllerAdvice rather than in each controller.
#Builder Methods
| Builder | HTTP status |
|---|---|
ok() | 200 |
created(uri) | 201 |
accepted() | 202 |
noContent() | 204 |
badRequest() | 400 |
notFound() | 404 |
unprocessableEntity() | 422 |
internalServerError() | 500 (Spring 5.3.8+) |
status(...) | Any status |
These make code much cleaner.
#Custom Headers
@GetMapping
public ResponseEntity<String> hello() {
return ResponseEntity
.ok()
.header("Application", "Ticket-System")
.header("Version", "1.0")
.body("Hello");
}HTTP/1.1 200 OK
Application: Ticket-System
Version: 1.0#Cookies
Cookies are sent through headers:
ResponseCookie cookie = ResponseCookie.from("token", "abc123")
.httpOnly(true)
.build();
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body("Logged In");#Files, Images and Bytes
File Download
@GetMapping("/download")
public ResponseEntity<Resource> download() {
Resource file = ...;
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=report.pdf")
.body(file);
}The browser downloads report.pdf.
Image
@GetMapping("/logo")
public ResponseEntity<Resource> image() {
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_PNG)
.body(resource);
}Byte Array
@GetMapping("/pdf")
public ResponseEntity<byte[]> pdf() {
byte[] data = service.generatePdf();
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.body(data);
}Empty Body
return ResponseEntity.ok().build(); // 200 OK, no body#Generic Response Wrapper
Enterprise applications often return a common response structure:
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<User>> get() {
}{
"success": true,
"message": "User Found",
"data": {
"id": 1,
"name": "Rahul"
}
}Useful for maintaining a consistent API contract.
#ResponseEntity vs Returning an Object
Returning an object (public User get()) | Returning ResponseEntity<User> |
|---|---|
| Spring decides status and headers — mostly defaults | You decide status, headers and body |
| Complete control |
#ResponseEntity vs @ResponseStatus
// @ResponseStatus — the status is fixed and can't change dynamically
@ResponseStatus(HttpStatus.CREATED)
@PostMapping
public User save() {
}
// ResponseEntity — dynamic; preferred in enterprise APIs
if (success) {
return ResponseEntity.ok(user);
}
return ResponseEntity.badRequest().build();#Enterprise Architecture
#Internal Spring Working
The important internal class is HttpEntityMethodProcessor — it processes ResponseEntity and HttpEntity. An interview favourite.
#Common Mistakes
Returning 200 After Creation
return ResponseEntity.ok(saved); // ❌
return ResponseEntity.status(HttpStatus.CREATED).body(saved); // ✅
return ResponseEntity.created(location).body(saved); // ✅Returning Entities Directly
ResponseEntity<User> → better ResponseEntity<UserResponse>. Return DTOs instead of JPA entities.
Catching Every Exception in Controllers
try {
...
} catch (Exception e) {
return ResponseEntity.status(500).build(); // ❌
}Prefer a global exception handler with @ControllerAdvice.
Returning String Errors Everywhere
return ResponseEntity.badRequest().body("Error"); // ❌Better — a structured error response:
{
"timestamp": "...",
"status": 400,
"message": "Validation Failed"
}#Best Practices
- Return DTOs, not entities.
- Use appropriate HTTP status codes.
- Include a
Locationheader for newly created resources. - Use
204 No Contentwhen there is no response body. - Keep success and error responses consistent.
- Handle exceptions globally with
@ControllerAdvice.
#Interview Questions
Q1What is ResponseEntity?
It represents the complete HTTP response, including the status code, headers and body.
Q2Why use ResponseEntity instead of returning an object?
Because it allows full control over the HTTP status, headers and response body.
Q3What status code should be returned after creating a resource?
201 Created, preferably with a Location header pointing to the new resource.
Q4What should DELETE return?
Usually 204 No Content.
Q5What is the difference between ResponseEntity and @ResponseStatus?
| ResponseEntity | @ResponseStatus |
|---|---|
| Dynamic | Static |
| Can change per request | Fixed at compile time |
| Controls headers, status and body | Primarily sets the status |
Q6Which internal Spring class handles ResponseEntity?
HttpEntityMethodProcessor.
Q7Can ResponseEntity return files?
Yes — Resource, byte[], streams, JSON, XML and plain text.
Q8Can we add custom headers?
Yes:
ResponseEntity.ok()
.header("Version", "1.0")
.body(data);#Key Takeaways
ResponseEntitygives complete control over the HTTP response.- Use it to customize status codes, headers and response bodies.
- Prefer
201 Created(with aLocationheader) for resource creation. - Use
204 No Contentfor deletes with no body. HttpEntityMethodProcessorprocessesResponseEntity.- Prefer
ResponseEntityover@ResponseStatuswhen the status depends on runtime logic. - Return DTOs and structured error responses for enterprise-grade APIs.