skip to content

What exactly does the reason attribute of @ResponseStatus (or the reason of ResponseStatusException) do to the response, and how does MessageSource / Spring Boot's error properties affect it?

level: seniorimportance: should knowfreq 45%

answer

  1. reason → sendError(status, reason)
  2. sendError commits + forwards to /error → BasicErrorController builds body
  3. Boot include-message defaults to 'never' — reason hidden
  4. Spring 5.3: reason is a MessageSource code (i18n)
  5. @ExceptionHandler bypasses the sendError path

basics

~20 s

With a reason, the ResponseStatusExceptionResolver calls HttpServletResponse.sendError(status, reason) instead of sendError(status). sendError triggers the servlet error page (in Boot, /error and BasicErrorController), which builds the body. Since Spring 5.3 the reason can be resolved as a message code via MessageSource.

solid answer

~50 s

The reason changes how the resolver writes the response. Without a reason the ResponseStatusExceptionResolver calls response.sendError(statusCode); with a reason it calls response.sendError(statusCode, reason). sendError commits the response and hands control to the servlet container's error mechanism — in Spring Boot that means a forward to /error handled by BasicErrorController, which assembles the JSON/HTML body. Consequently the reason does not become the response body directly; it flows in as the container's error message and appears (or not) depending on the error attributes. Notably, Spring Boot suppresses the message by default: server.error.include-message is 'never', so the reason won't show unless set to 'always' or 'on_param'. Since Spring 5.3, the resolver can treat reason as a MessageSource code, so you can externalize/i18n it. Because sendError drives the flow, an @ExceptionHandler that writes its own body bypasses all this.

code

java · 10 lines
java
// messages.properties:  error.product.notFound=Product {0} was not found

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "error.product.notFound")
public class ProductNotFoundException extends RuntimeException { }

// application.properties
// server.error.include-message=always   // otherwise 'message' is blank by default

// Programmatic equivalent; reason also runs through MessageSource since 5.3
// throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.product.notFound");

go deeper

for a junior

Know reason is an explanatory message attached to the status.

for a middle

Know reason triggers sendError(status, reason) rather than sendError(status).

for a senior

Explain the /error + BasicErrorController flow, include-message default, and MessageSource resolution.

for a principal

Weigh reason/sendError vs central ProblemDetail advice for a consistent, safe error contract.

## What reason is `reason` is an optional attribute on `@ResponseStatus` and a constructor argument of `ResponseStatusException`. It is a human-readable explanation of the error. ## The mechanical difference: sendError Inside `ResponseStatusExceptionResolver.applyStatusAndReason(...)`: - **No reason** → `response.sendError(statusCode)`. - **With reason** → `response.sendError(statusCode, resolvedReason)`. `HttpServletResponse.sendError(...)` is a Servlet API call that (a) sets the status, (b) **commits** the response, and (c) tells the container to use its error-handling mechanism. It does NOT write your string as the raw body. In Spring Boot the container is configured to forward to the `/error` path, handled by `BasicErrorController`, which builds the actual body from the current `ErrorAttributes`. ## Where the reason ends up (and why it may vanish) Because the body is built by `BasicErrorController` from error attributes, the reason surfaces as the `message` attribute — subject to Boot's configuration: - `server.error.include-message` defaults to **`never`** (since Boot 2.3), so by default the `message` field is blank/omitted. Set it to `always` or `on_param` (`?message=...`) to expose it. - `server.error.include-exception`, `include-stacktrace`, `include-binding-errors` similarly gate other fields. This is a classic gotcha: developers set a nice `reason`, get a generic body, and think the reason was ignored — it was suppressed by `include-message=never`. ## MessageSource / i18n (Spring 5.3+) `ResponseStatusExceptionResolver` has `setMessageSource(MessageSource)` and is `MessageSourceAware`; in a Spring Boot app the context's `MessageSource` is injected. Since Spring 5.3 the `reason` is treated as a **message code**: if a matching entry exists in your `messages.properties`, the resolved (and possibly localized) text is used; otherwise the literal `reason` string is used as-is. So `@ResponseStatus(code = NOT_FOUND, reason = "error.product.notFound")` can resolve to a localized message. ## ResponseStatusException reason Same idea: `ResponseStatusException.getReason()` feeds the resolver, which likewise runs it through `sendError` and MessageSource. `ResponseStatusException` also has `getMessage()` combining status and reason for logs. ## Interaction with the resolver chain / @ExceptionHandler The reason/`sendError` path only applies when the `ResponseStatusExceptionResolver` handles the exception. If an `@ExceptionHandler` (via `ExceptionHandlerExceptionResolver`, which runs earlier) catches the exception and writes a `ResponseEntity`/`@ResponseBody`, that method controls the body entirely and the `sendError`/`/error` machinery is bypassed — your reason won't be used unless you read it yourself. ## Spring 6 ProblemDetail If the exception instead reaches `ResponseEntityExceptionHandler` (Spring 6), a `ProblemDetail` (RFC 7807) is produced and the reason maps to the problem `detail`, not the servlet error message. Which path applies depends on whether such a handler is registered. ## Practical guidance - Don't rely on `reason` alone for API error bodies; either flip `include-message=always` (careful about leaking internals) or, better, use a central `@ControllerAdvice`/`@ExceptionHandler` returning a structured body (or `ProblemDetail`) so you fully control the payload. - Use `reason` as a message code when you want i18n with minimal wiring.

  • A teammate set reason but the JSON response shows an empty message. Why?
    Spring Boot's server.error.include-message defaults to 'never', so BasicErrorController omits the message. Set it to 'always' (or 'on_param') — or return a custom body via @ControllerAdvice.
  • Does the reason string become the raw HTTP response body?
    No. It is passed to HttpServletResponse.sendError(status, reason), which commits the response and delegates to the container's error handling (/error, BasicErrorController); that machinery builds the body, exposing the reason only as the 'message' attribute if configured.
  • How do you internationalize the reason?
    Since Spring 5.3 the ResponseStatusExceptionResolver runs reason through the context MessageSource, so use a message code (e.g. error.product.notFound) with entries in messages.properties.

saying these in an interview costs you the question

  • Believing the reason string is written verbatim as the response body
  • Not knowing include-message=never hides the reason by default
  • Thinking reason works only as literal text and never via MessageSource

context