skip to content

What is the difference between declarative @ResponseStatus on an exception class and programmatically throwing a ResponseStatusException? When would you choose each?

level: middleimportance: must knowfreq 72%

answer

  1. Annotation = static per type; exception = dynamic per throw
  2. ResponseStatusException: status + reason + headers, one class
  3. Same ResponseStatusExceptionResolver handles both
  4. Annotation → class explosion; exception → anonymous
  5. Spring 6: both can yield ProblemDetail

basics

~20 s

@ResponseStatus is a fixed status baked into an exception class. ResponseStatusException lets you throw an exception with a status (and reason) chosen at runtime, without creating a new class. Use the annotation for stable domain errors, the exception for ad-hoc/dynamic cases.

solid answer

~40 s

Both end up routed through the same ResponseStatusExceptionResolver, but they differ in flexibility. @ResponseStatus(HttpStatus.X) on a custom exception is declarative and static — the status is fixed per type, so you need a separate exception class per status. ResponseStatusException (Spring 5+) is programmatic: you construct new ResponseStatusException(status, reason) and throw it, choosing the status and reason at runtime from a single generic type. It also supports response headers via its getHeaders(). Choose the annotation for a small, well-defined set of recurring domain errors that map one-to-one to a status and where a named type improves readability and lets callers catch it. Choose ResponseStatusException for quick, localized, or dynamically-computed errors where minting a dedicated class would be overkill — common in service/controller code that just needs a 404 or 409 inline.

code

java · 19 lines
java
// Declarative: fixed mapping, named type
@ResponseStatus(HttpStatus.CONFLICT)
public class DuplicateEmailException extends RuntimeException {
    public DuplicateEmailException(String email) { super(email); }
}

// Programmatic: dynamic status/reason + a header, no new class
@GetMapping("/reports/{id}")
public ReportDto get(@PathVariable String id) {
    Report r = repo.find(id).orElseThrow(() ->
        new ResponseStatusException(HttpStatus.NOT_FOUND, "no report " + id));
    if (!r.isReady()) {
        ResponseStatusException ex =
            new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "still generating");
        ex.getHeaders().add(HttpHeaders.RETRY_AFTER, "30");
        throw ex;
    }
    return toDto(r);
}

go deeper

for a junior

Know one is an annotation, the other is thrown at runtime.

for a middle

Explain static-vs-dynamic and the class-explosion tradeoff clearly.

for a senior

Mention headers, the shared resolver, and WebFlux/MVC reuse.

for a principal

Discuss error-model strategy: named domain exceptions + central advice vs inline ResponseStatusException, and ProblemDetail in Spring 6.

## The two mechanisms **Declarative — @ResponseStatus on an exception class** ```java @ResponseStatus(HttpStatus.CONFLICT) public class DuplicateEmailException extends RuntimeException { } ``` The status is part of the type. Every instance yields 409. To also express 404 you must write another class. **Programmatic — ResponseStatusException** ```java throw new ResponseStatusException(HttpStatus.NOT_FOUND, "user " + id + " not found"); ``` `ResponseStatusException` (added in Spring 5.0, package `org.springframework.web.server`) is a `RuntimeException` that carries the status, an optional reason, and optional headers. One type covers every status. ## Same resolver, different ergonomics Both are handled by the `ResponseStatusExceptionResolver`. For a `ResponseStatusException` the resolver calls `getStatusCode()`/`getReason()`; for other exceptions it looks up `@ResponseStatus` via `AnnotatedElementUtils` on the exception class. Net effect on the wire is the same kind of response. ## Differences that matter 1. **Static vs dynamic.** Annotation = one status per type; exception = any status decided at throw time. 2. **Class explosion.** Heavy reliance on annotated exceptions means many tiny classes. `ResponseStatusException` avoids that. 3. **Headers.** `ResponseStatusException` exposes `getHeaders()` (since Spring 5.1.13/5.2) so you can attach things like `Retry-After` or `Allow`; the annotation cannot add headers. 4. **Catchability / semantics.** A named exception type (annotated) is catchable and testable by type and communicates domain meaning; a generic `ResponseStatusException` is more anonymous. 5. **Reuse across transports.** `ResponseStatusException` lives in `web.server` and is understood by both Spring MVC and WebFlux. ## Spring 6 note In Spring 6, `ResponseStatusException` implements the `ErrorResponse` interface and can render an RFC 7807 `ProblemDetail` body when processed by `ResponseEntityExceptionHandler`. `@ResponseStatus` exceptions get a `ProblemDetail` too when they reach that handler. ## Choosing - **Annotation:** stable, recurring domain errors with a clean 1:1 status mapping; you want a named, catchable type; you want the mapping visible on the type. - **ResponseStatusException:** ad-hoc or computed status, one-off validation/lookup failures, when you also need custom headers, or when you don't want to create a class. - Many teams combine both: named annotated exceptions for the core domain, `ResponseStatusException` for the long tail.

  • Can @ResponseStatus attach a custom response header like Retry-After?
    No. The annotation only sets status and reason. To add headers you need ResponseStatusException (getHeaders()) or an @ExceptionHandler/@ControllerAdvice that returns a ResponseEntity.
  • Is there a downside to using ResponseStatusException everywhere?
    You lose named, domain-meaningful, catchable types and the mapping is scattered across throw sites rather than declared once. It can also make consistent error bodies harder without a central @ControllerAdvice.

saying these in an interview costs you the question

  • Claiming ResponseStatusException and @ResponseStatus are handled by different, incompatible mechanisms
  • Saying @ResponseStatus can vary its status at runtime
  • Thinking ResponseStatusException requires a custom exception subclass

context