Responses & ResponseEntity
Responses & ResponseEntity
In the previous lessons you learned how to map incoming requests and bind their data. Now the focus shifts to the outbound side: how Spring Boot translates your return value into an HTTP response, and how ResponseEntity gives you precise control over every part of that response — the status code, the headers, and the body.
The Default: Return a POJO and Let Spring Handle It
When a method inside a @RestController returns a plain Java object, Spring's HttpMessageConverter infrastructure serialises it to JSON (or XML) automatically. The HTTP status defaults to 200 OK and the Content-Type header is set to application/json.
This is perfectly fine for the happy path, but what about when the resource is not found? Or when you create something and must return 201 Created with a Location header? For those cases you need ResponseEntity.
ResponseEntity — Full Response Control
ResponseEntity<T> is a generic wrapper that bundles a body of type T, an HttpStatus, and a set of HttpHeaders into a single return value. It is part of org.springframework.http and works in any Spring MVC or Spring WebFlux controller.
ResponseEntity<T> rather than T directly? Because HTTP is more than a carrier for JSON. Status codes communicate intent to clients, middleware, and monitoring systems. Returning an object with a hard-coded 200 when the resource was not found forces the client to inspect the body to discover the failure — that is a leaky contract.
The Builder API
Constructing a ResponseEntity through its constructors directly is verbose. The fluent builder API (available since Spring 4) is much cleaner:
Setting Custom Headers
Headers carry metadata that is not part of the body: pagination cursors, rate-limit information, cache directives, and more. You add them through HttpHeaders or directly on the builder:
The Location header set by .created(uri) tells the client exactly where to find the newly created resource. REST clients, hypermedia frameworks, and API gateways all rely on this.
Controlling the Status Code Explicitly
Use ResponseEntity.status(HttpStatus.XXX) whenever the right code is not covered by the convenience factory methods. For example, returning 202 Accepted for an asynchronous task:
Returning No Body
Several HTTP operations should return an empty body: successful DELETE, successful PUT when you do not echo the updated resource, and acknowledgements. Use ResponseEntity<Void> to communicate this clearly at the type level:
ResponseEntity<Void> over ResponseEntity<?> for empty-body responses. It makes the contract explicit in the method signature: callers and code-generation tools know there is intentionally no body, rather than wondering whether the wildcard hides something.
Conditional Responses with ETags and If-None-Match
For read-heavy endpoints you can support HTTP conditional requests to reduce bandwidth. Spring provides ShallowEtagHeaderFilter for automatic ETag generation, but you can also set etag headers manually via ResponseEntity when you need fine-grained control:
Typed vs. Wildcard ResponseEntity
You will sometimes see methods declared as ResponseEntity<?> or even ResponseEntity<Object>. This is a code smell in most cases. It sacrifices compile-time type safety, confuses IDE tooling, and breaks OpenAPI schema generation. The only legitimate use is when a single endpoint genuinely returns different body types depending on outcome — and even then, a proper exception handler (covered in a later lesson on error handling) is usually the better design.
ResponseEntity<?> as a lazy catch-all. When you find yourself wanting to return either a ProductDto or an error string from the same method, that is a sign the error case should be an exception thrown from the service and translated by a @ControllerAdvice class — not a second code path inside the controller method.
Practical Pattern: the Service Returns an Optional
A clean, idiomatic pattern for GET endpoints is to have the service return an Optional and map it to the appropriate ResponseEntity in the controller:
This pattern keeps the controller thin, keeps the null-handling explicit, and avoids a conditional branch that is easy to forget.
Summary
Spring Boot serialises plain return values to JSON with a default status of 200. ResponseEntity<T> lifts that default, giving you first-class control over the status code, headers, and body in a single type-safe object. Use the fluent builder API — ResponseEntity.ok(), .created(uri), .noContent(), .status(HttpStatus.X) — for readable, intention-revealing code. Type your ResponseEntity with a concrete type parameter; reach for <Void> when the body is intentionally empty. In the next lesson you will deepen your understanding of the HTTP status codes themselves and the REST semantics they carry.