'Spring ResponseStatusException does not return reason

I have a very simple @RestController, and I'm trying to set a custom error message. But for some reason, the message for the error is not showing up.

This is my controller:

@RestController
@RequestMapping("openPharmacy")
public class OpenPharmacyController {


    @PostMapping
    public String findNumberOfSurgeries(@RequestBody String skuLockRequest) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "This postcode is not valid");
    }

}

This is the response that I get:

{
    "timestamp": "2020-06-24T17:44:20.194+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "",
    "path": "/openPharmacy/"
}

I'm passing a JSON, but I'm not validating anything, I'm just trying to set the custom message. If I change the status code, I see that on the response, but the message is always empty.

Why is this not working like expected? This is such a simple example that I can't see what may be missing. When I debug the code I can see that the error message has all the fields set. But for some reason, the message is never set on the response.



Solution 1:[1]

I have the very same issue. If I use this construct

throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Error in update");

My message is not passed to client via JSON. For me, the only way to go around it was to create GlobalExceptionHandler class

package mypackage;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.Date;

@ControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(NotFoundException.class)
  public ResponseEntity<ErrorDTO> generateNotFoundException(NotFoundException ex) {
    ErrorDTO errorDTO = new ErrorDTO();
    errorDTO.setMessage(ex.getMessage());
    errorDTO.setStatus(String.valueOf(ex.getStatus().value()));
    errorDTO.setTime(new Date().toString());

    return new ResponseEntity<ErrorDTO>(errorDTO, ex.getStatus());
  }
}

I have also created my own Exception type

package mypackage;

import org.springframework.http.HttpStatus;

public class NotFoundException extends RuntimeException {

  public NotFoundException(String message) {
    super(message);
  }

  public HttpStatus getStatus() {
    return HttpStatus.NOT_FOUND;
  }
}

With this, I am able to throw exception from the controller and I am getting proper result in JSON - the message I want to see.

@PutMapping("/data/{id}")
public DataEntity updateData(@RequestBody DataEntity data, @PathVariable int id) {
  throw new NotFoundException("Element not found");
}

I had to introduce ErrorDTO as well

package mypackage;

public class ErrorDTO {
  public String status;
  public String message;
  public String time;

  ...
  ...
  // getters and setters are here 
  ...
  ...
}

Update

As mentioned by @Hassan and @cunhaf (in comments under original question), the solution with

server.error.include-message=always

works perfectly fine with ResponseStatusException. Still, solution with GlobalExceptionHandler might be better in case someone wants to pass more info via Exception.

Source code

Samples can be found here: Global Exception Handler

Solution 2:[2]

Strangely, Spring Boot 2.6.x changed this behavior again and the error message set on ResponseStatusException is not returned. I had to downgrade to 2.5.6 in order to solve it. In the end I had something like this:

 @DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.OK)  
public MessageResponse deleteById(@PathVariable(value = "id") Integer id) {
    try {
        userService.deleteById(id); 
        
    } catch (Exception e) {
        throw new ResponseStatusException(HttpStatus.EXPECTATION_FAILED, "Error deleting user. User has dependencies", e);
    }
}

Solution 3:[3]

there is bean that can be overridden to include custom message.

Normal Spring Boot: org.springframework.boot.web.servlet.error.ErrorAttributes

Spring Wedbflux: org.springframework.boot.web.reactive.error.ErrorAttributes


the default implementation is DefaultErrorAttributes.

You can override public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {

the return make it return the message you want


In my case I created a decorator that will remove messages if it's internal server error:

public class CustomErrorAttributesDecorator implements ErrorAttributes {

    private final ErrorAttributes errorAttributes;
    
    CustomErrorAttributesDecorator(ErrorAttributes errorAttributes){
        this.errorAttributes = errorAttributes;
    }

    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributesMap = this.errorAttributes.getErrorAttributes(request, options);
        if(HttpStatus.INTERNAL_SERVER_ERROR.value() == (int) errorAttributesMap.get("status")){
            errorAttributesMap.remove("message");
        }
        return errorAttributesMap;
    }

...
}

and then I created a @Bean as follows:

        @Bean
        ErrorAttributes customErrorAttributes(){
            return new CustomErrorAttributesDecorator(new DefaultErrorAttributes());
        }

Solution 4:[4]

Starting from the 2.3 version, Spring Boot doesn't include an error message on the default error page. The reason is to reduce the risk of leaking information to a client

To change the default behavior, we can use a server.error.include-message property.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Digao
Solution 3
Solution 4 Raj N