Spring Boot
ResponseStatusException
exception handling
error messages
REST API

Exception message not included in response when throwing ResponseStatusException in Spring Boot

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When you throw a ResponseStatusException with a reason message in Spring Boot 2.3+, the message does not appear in the JSON error response by default. This is because Spring Boot changed the default behavior to hide error details for security reasons — exposing internal error messages to clients can leak implementation details. To include the message, you must set server.error.include-message=always in application.properties, or use a custom error handler.

The Problem

java
1@GetMapping("/users/{id}")
2public User getUser(@PathVariable Long id) {
3    return userRepository.findById(id)
4        .orElseThrow(() -> new ResponseStatusException(
5            HttpStatus.NOT_FOUND, "User not found with id: " + id
6        ));
7}

Expected response:

json
1{
2  "status": 404,
3  "error": "Not Found",
4  "message": "User not found with id: 42"
5}

Actual response (Spring Boot 2.3+):

json
1{
2  "status": 404,
3  "error": "Not Found",
4  "message": ""
5}

The message field is empty even though you passed "User not found with id: 42" to the exception.

Fix 1: Configure application.properties

properties
# application.properties
server.error.include-message=always

Or in YAML:

yaml
1# application.yml
2server:
3  error:
4    include-message: always

The options for include-message are:

  • never (default in Spring Boot 2.3+) — always empty
  • always — always included
  • on_param — included only when the request has a message parameter

After this change, the reason from ResponseStatusException appears in the response:

json
1{
2  "timestamp": "2025-03-02T10:15:30.000+00:00",
3  "status": 404,
4  "error": "Not Found",
5  "message": "User not found with id: 42",
6  "path": "/users/42"
7}

Fix 2: Custom Exception Handler with @ControllerAdvice

For full control over the error response format, use @ControllerAdvice:

java
1@RestControllerAdvice
2public class GlobalExceptionHandler {
3
4    @ExceptionHandler(ResponseStatusException.class)
5    public ResponseEntity<Map<String, Object>> handleResponseStatusException(
6            ResponseStatusException ex) {
7
8        Map<String, Object> body = new LinkedHashMap<>();
9        body.put("timestamp", LocalDateTime.now());
10        body.put("status", ex.getStatusCode().value());
11        body.put("error", ex.getStatusCode().toString());
12        body.put("message", ex.getReason());
13
14        return new ResponseEntity<>(body, ex.getStatusCode());
15    }
16}

This bypasses Spring Boot's default error handling entirely and lets you include or exclude any field.

Fix 3: Custom Error Attributes

Override DefaultErrorAttributes to customize what Spring Boot's default error response includes:

java
1@Component
2public class CustomErrorAttributes extends DefaultErrorAttributes {
3
4    @Override
5    public Map<String, Object> getErrorAttributes(
6            WebRequest webRequest, ErrorAttributeOptions options) {
7
8        // Include the message
9        options = options.including(ErrorAttributeOptions.Include.MESSAGE);
10
11        Map<String, Object> attrs = super.getErrorAttributes(webRequest, options);
12
13        // Optionally add custom fields
14        attrs.put("service", "user-service");
15
16        // Optionally remove fields
17        attrs.remove("trace");
18
19        return attrs;
20    }
21}

Fix 4: Custom Exception Classes

Instead of ResponseStatusException, define specific exception classes for cleaner error handling:

java
1@ResponseStatus(HttpStatus.NOT_FOUND)
2public class ResourceNotFoundException extends RuntimeException {
3    public ResourceNotFoundException(String message) {
4        super(message);
5    }
6}
java
1@RestControllerAdvice
2public class GlobalExceptionHandler {
3
4    @ExceptionHandler(ResourceNotFoundException.class)
5    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
6        ErrorResponse error = new ErrorResponse(
7            HttpStatus.NOT_FOUND.value(),
8            ex.getMessage(),
9            LocalDateTime.now()
10        );
11        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
12    }
13
14    @ExceptionHandler(IllegalArgumentException.class)
15    public ResponseEntity<ErrorResponse> handleBadRequest(IllegalArgumentException ex) {
16        ErrorResponse error = new ErrorResponse(
17            HttpStatus.BAD_REQUEST.value(),
18            ex.getMessage(),
19            LocalDateTime.now()
20        );
21        return ResponseEntity.badRequest().body(error);
22    }
23}
24
25record ErrorResponse(int status, String message, LocalDateTime timestamp) {}
java
1// Usage in controller
2@GetMapping("/users/{id}")
3public User getUser(@PathVariable Long id) {
4    return userRepository.findById(id)
5        .orElseThrow(() -> new ResourceNotFoundException(
6            "User not found with id: " + id
7        ));
8}

Other Hidden Error Properties

Spring Boot 2.3+ also hides stack traces and binding errors by default:

properties
1# Show everything (development only!)
2server.error.include-message=always
3server.error.include-stacktrace=on_param
4server.error.include-binding-errors=always
5server.error.include-exception=true

For production, keep the defaults (never) and use @ControllerAdvice to return only safe, user-facing messages.

Spring Boot Version History

VersionDefault include-messageBehavior
Spring Boot 2.0-2.2Messages included by defaultReason appears in response
Spring Boot 2.3+neverMessages hidden by default
Spring Boot 3.xneverSame as 2.3+, uses Jakarta namespace

The change was introduced in Spring Boot 2.3 (May 2020) as a security improvement to prevent accidental information leakage.

Common Pitfalls

  • Not knowing the default changed in Spring Boot 2.3: Code that worked in Spring Boot 2.2 silently stops showing error messages after upgrading. Add server.error.include-message=always if you relied on the old behavior.
  • Exposing internal details in production: Setting include-message=always in production can leak database column names, query details, or internal paths. Use @ControllerAdvice to sanitize messages for production and include-message=always only in development profiles.
  • Confusing reason with message: ResponseStatusException uses getReason(), not getMessage(). The getMessage() method returns the HTTP status description combined with the reason, while getReason() returns just your custom message.
  • Using @ResponseStatus on the exception class AND throwing ResponseStatusException: If you annotate a custom exception with @ResponseStatus and also wrap it in ResponseStatusException, the status codes may conflict. Use one approach consistently.
  • Forgetting to handle exceptions in reactive WebFlux: In WebFlux, ResponseStatusException works differently — it returns the message by default. The server.error.include-message property only applies to the servlet (MVC) stack.

Summary

  • Spring Boot 2.3+ hides ResponseStatusException reason messages by default for security
  • Set server.error.include-message=always in application.properties for the quickest fix
  • Use @RestControllerAdvice with @ExceptionHandler for full control over error responses
  • Define custom exception classes instead of ResponseStatusException for cleaner, more maintainable error handling
  • Keep error messages hidden in production and use structured error responses to avoid leaking internal details

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.