skip to content

Compare @ResponseStatus on a (void) handler with returning a ResponseEntity. When would you choose each, and how do they interact if both are present?

level: middleimportance: should knowfreq 60%

answer

  1. @ResponseStatus = static/declarative status
  2. ResponseEntity = runtime/per-request status
  3. both present → ResponseEntity status wins
  4. void handler defaults to 200, not 204
  5. reason attribute → sendError, replaces body

basics

~20 s

@ResponseStatus declares a fixed status for a handler (or exception) at compile time — good when the status never varies, often with void handlers returning no body. ResponseEntity sets status per-response at runtime. If both apply, the ResponseEntity's status wins.

solid answer

~40 s

@ResponseStatus(HttpStatus.X) is a static, declarative status annotation you put on a handler method, controller class, or an exception class. On a void handler it produces that status with no body — handy for creates/deletes/side-effect endpoints where the status is always the same (e.g. @ResponseStatus(CREATED) on a POST). ResponseEntity sets status imperatively per invocation, so it's the choice when the same handler can return different statuses (200 vs 404). They can coexist: a method-level @ResponseStatus sets the default, but if that handler returns a ResponseEntity, the status carried by the ResponseEntity takes precedence for that response. @ResponseStatus is also the standard way to map exceptions to statuses, either directly on a custom exception or via @ExceptionHandler methods.

code

java · 14 lines
java
// Declarative: status never varies, no body
@ResponseStatus(HttpStatus.CREATED)   // 201
@PostMapping("/tags")
public void addTag(@RequestBody @Valid TagDto dto) {
    tagService.add(dto);
}

// Imperative: status varies per request, needs a header
@GetMapping("/tags/{id}")
public ResponseEntity<TagDto> get(@PathVariable Long id) {
    return tagService.find(id)
        .map(t -> ResponseEntity.ok().eTag(t.version()).body(t)) // 200 + ETag
        .orElseGet(() -> ResponseEntity.notFound().build());     // 404
}

go deeper

for a junior

Know @ResponseStatus fixes a status and void handlers send no body.

for a middle

Explain static-vs-runtime tradeoff and the void→200 default; know the reason/sendError gotcha.

for a senior

Describe precedence when both are present and exception-to-status mapping options.

for a principal

Advocate one mechanism per handler and a team-wide convention (declarative for fixed, ResponseEntity for variable/headers), plus ProblemDetail for errors.

## The two mechanisms **`@ResponseStatus`** (`org.springframework.web.bind.annotation.ResponseStatus`) is a **declarative, compile-time** way to set the HTTP status. You can place it on: - a **handler method** — every normal return uses that status; - a **controller class** — default for all its handlers; - a **custom exception class** — when that exception propagates, Spring returns the given status; - an **`@ExceptionHandler` method**. Its attributes: `code`/`value` (the `HttpStatus`) and `reason` (an optional message). When `reason` is set on an exception, Spring uses `HttpServletResponse.sendError(...)`, which routes through the container error page — a notable side effect. **`ResponseEntity`** sets the status **imperatively at runtime**: whatever `HttpStatus` you build into it is used for that specific response. ## void handlers A handler that returns `void` (or `ResponseEntity<Void>`) sends **no body**. Pairing a `void` handler with `@ResponseStatus` is the clean way to express "do the side effect, return status N, no body": ```java @ResponseStatus(HttpStatus.NO_CONTENT) // 204 @DeleteMapping("/sessions/{id}") public void logout(@PathVariable String id) { sessions.revoke(id); } ``` Without `@ResponseStatus`, a `void` handler defaults to **200 OK** (with an empty body). ## When to choose which - **Fixed status, no per-request variation** → `@ResponseStatus` (declarative, less boilerplate). Great for 201-on-create, 204-on-delete, 202-on-accept. - **Status varies per request** (found → 200, missing → 404; conditional → 304) → `ResponseEntity`, because you decide at runtime. - **Need custom headers** (Location, ETag, Cache-Control) → `ResponseEntity` (or inject `HttpServletResponse`). `@ResponseStatus` only sets the status/reason, not arbitrary headers. - **Exception → status mapping** → `@ResponseStatus` on the exception, or an `@ExceptionHandler` returning a `ResponseEntity`/`ProblemDetail`. ## Interaction when both are present If a method carries `@ResponseStatus` **and** returns a `ResponseEntity`, the **status in the ResponseEntity wins** for responses that go through it. The annotation acts as the default only when the return path doesn't itself specify a status. Relying on this overlap is confusing — prefer one mechanism per handler. ## Gotchas - `@ResponseStatus` with a `reason` triggers `sendError`, so the body is replaced by the servlet container's error response (or your error page), not your DTO. Omit `reason` if you want to keep a JSON body. - `@ResponseStatus` is **static** — you cannot compute the code from request data; for that you must use `ResponseEntity`. - On modern Spring (6+), exception-to-status is increasingly expressed via `ProblemDetail`/`ErrorResponse`; `@ResponseStatus` on exceptions still works and is common. - A `void` handler is **not** the same as a 204 by default — it is **200 with an empty body** unless annotated.

  • What status does a void handler with no @ResponseStatus return?
    200 OK with an empty body. It is a common misconception that void means 204; you must annotate with @ResponseStatus(HttpStatus.NO_CONTENT) to get 204.
  • Why might @ResponseStatus(reason = "...") drop your JSON body?
    Setting reason makes Spring call HttpServletResponse.sendError(), which hands off to the servlet container's error handling and replaces your intended body with the container/error-page response.

saying these in an interview costs you the question

  • Assuming a void handler returns 204 by default (it's 200)
  • Thinking @ResponseStatus can set arbitrary headers like Location
  • Believing @ResponseStatus can compute the status from request data at runtime

context