Spring ResponseStatusException does not return reason
JavaSpring BootRestError HandlingJava Problem Overview
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.
Java Solutions
Solution 1 - Java
This answer was provided by user Hassan in the comments on the original question. I'm only posting it as an answer to give it better visibility.
Basically, all you need to do is add server.error.include-message=always
to your application.properties file, and now your message field should be populated.
This behavior was changed in Spring Boot 2.3 which you can read about here: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#changes-to-the-default-error-pages-content
Solution 2 - Java
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 3 - Java
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 4 - Java
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 5 - Java
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.