🌐 ResponseEntity<T> β€” Spring’s HTTP Response Wrapper Cheatsheet

Essence: ResponseEntity represents the entire HTTP response: β†’ status code + headers + body (T) It’s a generic container returned by controller methods to give precise control over what the client receives.


1. Where It Lives

  • Package: org.springframework.http
  • Implements: HttpEntity<T> (adds HTTP status on top)
  • Used in: Spring MVC and Spring WebFlux
import org.springframework.http.ResponseEntity;

2. Why Use It

Use case Alternative Problem Solution with ResponseEntity
You need to set custom status return object directly Always returns 200 OK ResponseEntity.status(201)
You need to send headers @ResponseBody Can’t customize headers .header("X-Custom", "value")
You need conditional responses void or object No fine-grained control .notFound(), .noContent()
You want to return generic DTO or error JSON ResponseBody Limited flexibility ResponseEntity<?> handles both

3. Basic Forms

@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    Optional<User> user = repo.findById(id);
    return user.map(ResponseEntity::ok)
               .orElseGet(() -> ResponseEntity.notFound().build());
}

Equivalent results:

Return type Effect
User β†’ 200 OK (body = User, default headers)
ResponseEntity<User> β†’ 200, 404, 204, etc. (custom)

4. Factory Methods (Static Builders)

Method HTTP Status Example
ok(T body) 200 OK ResponseEntity.ok(user)
ok() 200 OK, no body ResponseEntity.ok().build()
created(URI location) 201 Created ResponseEntity.created(uri).build()
accepted() 202 Accepted ResponseEntity.accepted().build()
noContent() 204 No Content ResponseEntity.noContent().build()
badRequest() 400 Bad Request ResponseEntity.badRequest().body(error)
status(HttpStatus) custom ResponseEntity.status(HttpStatus.FORBIDDEN).build()
notFound() 404 Not Found ResponseEntity.notFound().build()

5. Builder Pattern API

return ResponseEntity.status(HttpStatus.CREATED)
                     .header("Location", "/api/user/42")
                     .contentType(MediaType.APPLICATION_JSON)
                     .body(savedUser);
  • Chaining allows combining headers, content type, and body in one statement.
  • Immutable β€” each call produces a new instance.

6. Anatomy of a ResponseEntity

ResponseEntity<T>
β”œβ”€ HttpStatus status
β”œβ”€ HttpHeaders headers
└─ T body

It’s a simple value object β€” no side effects or thread context.


7. Examples by Common Scenarios

βœ… Success (200)

return ResponseEntity.ok(user);

βž• Created (201)

URI location = URI.create("/api/users/" + user.getId());
return ResponseEntity.created(location).body(user);

🚫 No Content (204)

return ResponseEntity.noContent().build();

⚠️ Bad Request (400)

return ResponseEntity.badRequest()
                     .body(Map.of("error", "Invalid input"));

πŸ” Forbidden (403)

return ResponseEntity.status(HttpStatus.FORBIDDEN)
                     .build();

❌ Not Found (404)

return ResponseEntity.notFound().build();

βš™οΈ Custom Headers

return ResponseEntity.ok()
                     .header("X-App-Version", "1.0")
                     .body(data);

8. Relationship with HttpEntity and RequestEntity

Type Represents Direction Includes
HttpEntity<T> Only headers + body both request/response βœ… headers, βœ… body
RequestEntity<T> HTTP request metadata request + method + URI
ResponseEntity<T> Full HTTP response response + status code

All three share the same base idea: wrap HTTP metadata with body generically.


9. Working with ResponseEntity<Void>

Use Void when you want no body:

return ResponseEntity.noContent().build();   // same as ResponseEntity<Void>

10. Headers in Depth

Headers are stored in an immutable HttpHeaders map.

return ResponseEntity.ok()
                     .headers(h -> {
                         h.setCacheControl("no-cache");
                         h.add("X-Trace-Id", traceId);
                     })
                     .body(data);

Or build manually:

HttpHeaders headers = new HttpHeaders();
headers.set("X-Rate-Limit", "100");
return new ResponseEntity<>(data, headers, HttpStatus.OK);

11. Error Handling

Spring automatically serializes the body of a ResponseEntity β€” even for errors:

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                     .body(Map.of("error", "Invalid data"));

Use consistent DTOs:

record ApiError(String message, Instant timestamp) {}
return ResponseEntity.status(404)
                     .body(new ApiError("User not found", Instant.now()));

12. Content Negotiation

Spring respects the Accept header automatically:

  • ResponseEntity.ok().body(user) β†’ returns JSON or XML depending on the Accept type.
  • contentType(MediaType.APPLICATION_JSON) overrides it explicitly.

13. Using in Reactive Stack (WebFlux)

Same class, different behavior β€” it’s non-blocking:

@GetMapping("/reactive")
public Mono<ResponseEntity<User>> reactive() {
    return userService.findAsync()
                      .map(ResponseEntity::ok)
                      .defaultIfEmpty(ResponseEntity.notFound().build());
}

The ResponseEntity is wrapped in a Mono, not returned directly.


14. Integration with Exception Handling

When thrown from controllers, custom exceptions can produce ResponseEntity responses via:

@ControllerAdvice
public class ApiExceptionHandler {

  @ExceptionHandler(UserNotFoundException.class)
  public ResponseEntity<ApiError> handleUserNotFound(UserNotFoundException ex) {
    return ResponseEntity.status(HttpStatus.NOT_FOUND)
                         .body(new ApiError(ex.getMessage(), Instant.now()));
  }
}

15. Testing with MockMvc

mockMvc.perform(get("/user/42"))
       .andExpect(status().isOk())
       .andExpect(jsonPath("$.id").value(42));

Or unit test directly:

ResponseEntity<User> res = controller.getUser(42L);
assertEquals(HttpStatus.OK, res.getStatusCode());
assertNotNull(res.getBody());

16. Generics Power

ResponseEntity<List<User>> resp = ResponseEntity.ok(users);
List<User> body = resp.getBody();

Spring handles serialization for the list automatically.


17. Typical Usage Pattern

@GetMapping("/api/items")
public ResponseEntity<List<Item>> getAll() {
    List<Item> items = service.findAll();
    if (items.isEmpty())
        return ResponseEntity.noContent().build();
    return ResponseEntity.ok(items);
}

Concise, readable, fully controlled.


18. Immutable Design

  • Once built, a ResponseEntity can’t be modified.
  • Each chained method produces a new instance.
  • This makes it thread-safe and predictable inside Spring’s dispatching.

19. Common Pitfalls

Mistake Problem Fix
Returning null instead of ResponseEntity 500 Internal Server Error Use .noContent() or .notFound()
Forgetting .build() after header/status chain Compilation error Always end chain with .build() or .body()
Using wrong generic (e.g. ResponseEntity<Object>) Type confusion, serialization warnings Use concrete type or <?>
Adding headers after .build() Immutable β€” no effect Add before .build()
Mixing reactive and MVC types Mono<ResponseEntity> vs ResponseEntity Match controller type (WebFlux vs MVC)

20. Quick Reference Summary

Action Code
OK (200) ResponseEntity.ok(body)
Created (201) ResponseEntity.created(uri).body(body)
Accepted (202) ResponseEntity.accepted().build()
No Content (204) ResponseEntity.noContent().build()
Bad Request (400) ResponseEntity.badRequest().body(error)
Unauthorized (401) ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
Forbidden (403) ResponseEntity.status(HttpStatus.FORBIDDEN).build()
Not Found (404) ResponseEntity.notFound().build()
Custom Status ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT)
Add Header .header("X-Foo", "bar")
Set Content Type .contentType(MediaType.APPLICATION_JSON)

21. Mind Model Summary

Controller method
    ↓ returns
ResponseEntity<T>
    ↓
[Status] β†’ HttpStatus
[Headers] β†’ HttpHeaders
[Body] β†’ T (auto-serialized)

Spring converts it into a real HTTP response at the framework boundary.


Related Concept Role
HttpEntity<T> Base class without status
RequestEntity<T> HTTP request counterpart
HttpStatus Enum for standard codes
HttpHeaders Collection of header values
@ResponseBody Annotation for direct serialization
ResponseBodyAdvice Interceptor for modifying responses globally

23. Real-World Pattern

Spring REST APIs often structure controller responses as:

return ResponseEntity
         .status(HttpStatus.CREATED)
         .body(Map.of("id", savedId, "timestamp", Instant.now()));

Framework glue (e.g. @RestController) serializes it to JSON β†’ sets status/headers β†’ writes to network.


24. Bonus β€” Declarative vs Imperative Style

Declarative:

@GetMapping("/user/{id}")
@ResponseStatus(HttpStatus.CREATED)
public User createUser(...) { ... }

Imperative (using ResponseEntity):

@GetMapping("/user/{id}")
public ResponseEntity<User> createUser(...) {
    return ResponseEntity.status(HttpStatus.CREATED).body(user);
}

Declarative is simpler. Imperative (ResponseEntity) gives precision and dynamic control.


25. Final Mind Model

@ResponseBody return value ──► HttpMessageConverter
                               β”‚
                               β–Ό
                        ResponseEntity<T>
                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                     β”‚ status: HttpStatus  β”‚
                     β”‚ headers: HttpHeadersβ”‚
                     β”‚ body: T             β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β–Ό
                        Serialized β†’ HTTP

ResponseEntity is the universal adapter between Java objects and raw HTTP semantics β€” the final step in the Spring request pipeline.