Exception message not included in response when throwing ResponseStatusException in Spring Boot - spring-boot

My Spring Boot application provides the following REST controller:
#RestController
#RequestMapping("/api/verify")
public class VerificationController {
final VerificationService verificationService;
Logger logger = LoggerFactory.getLogger(VerificationController.class);
public VerificationController(VerificationService verificationService) {
this.verificationService = verificationService;
}
#GetMapping
public void verify(
#RequestParam(value = "s1") String s1,
#RequestParam(value = "s2") String s2) {
try {
verificationService.validateFormat(s1, s2);
} catch (InvalidFormatException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}
}
In case validateFormat() throws the InvalidFormatException the client gets a HTTP 400 which is correct. The default JSON response body however looks like this:
{
"timestamp": "2020-06-18T21:31:34.911+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/api/verify"
}
The message value is always empty even if I hard-code it like this:
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "some string");
This is the exception class:
public class InvalidFormatException extends RuntimeException {
public InvalidFormatException(String s1, String s2) {
super(String.format("Invalid format: [s1: %s, s2: %s]", s1, s2));
}
}

This behavior has changed with Spring Boot 2.3 and is intentional. See release notes for details.
Setting server.error.include-message=always in the application.properties resolves this issue.

Setting server.error.include-message=always disclosures messages of internal exceptions and this might be a problem in production environment.
An alternative approach is to use ExceptionHandler. Here you can control what is transferred to client:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<String> handleBadRequestException(ResponseStatusException ex) {
// if you want you can do some extra processing with message and status of an exception
// or you can return it without any processing like this:
return new ResponseEntity<>(ex.getMessage(), ex.getStatus());
}
}

Related

Spring Boot #ResponseStatus not returning HTTP message

I've a problem returning HTTP Messages when an exception is thrown. I'm using #ResponseStatus annotation to handle the HTTP Status code, it shows ok but the message is ignored.
Custom Exception:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "An error ocurred while trying to
retrieve the instruments.")
public class InstrumentsNotFoundException extends RuntimeException {
private static final Logger logger = LoggerFactory.getLogger(InstrumentsNotFoundException.class);
public InstrumentsNotFoundException(String errorMessage) {
super(errorMessage);
logger.error(errorMessage);
}
Controller:
#GetMapping({ "/portfolio/" })
public List<Instrument> getAll() {
try {
List<Instrument> instruments= portfolioYieldProcessor.getPortfolio();
return instruments;
} catch(RuntimeException e) {
throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, "Sorry, an error occurred, please try again later.", e);
}
}
HttpMessage
Starting from SpringBoot 2.3 this is the default behavior, as you can see here.
Assuming you are using spring 2.3+
server.error.include-message=always
in your application.properties will do the trick for you.

how to get access to the default spring error JSON

seems like by default Spring will return a message of:
{
"timestamp": "2019-01-17T16:12:45.977+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Error processing the request!",
"path": "/my-endpoint-with-exceptions"
}
currently the app is using #RestControllerAdvice with an #ExceptionHandler on each exception. The in each method it uses a ResponseEntity
#ExceptionHandler(GenericException.class)
public ResponseEntity<String> exceptionHandler(GenericException ex){
return new ResponseEntity<>(ex.getMessage,HttpStatus.BAD_REQUEST)
}
additionally seems like over time there have been any number of classes which do about the same time as the default which are used.
So would rather use the default Spring JSON however of course do not want to impact currently running code. So my question is for just the GenericException to return the default Spring JSON?
I did try to use ResponseStatusException which did return the JSON but for whatever reason would only return a INTERNAL_SERVER_ERROR (500) status even when setting the value in the argument.
You can define your own error response and return it from the exception handler method.
Something like this:
Model:
#Builder
public class ErrorResponse {
private int status;
private String error;
private String message;
private String path;
private long timestamp;
}
Handler:
#ExceptionHandler(GenericException.class)
public ResponseEntity<ErrorResponse> exceptionHandler(GenericException ex){
ErrorResponse errorResponse = ErrorResponse.builder()
.message(ex.getMessage())
.status(HttpStatus.BAD_REQUEST.value())
.error(HttpStatus.BAD_REQUEST.getReasonPhrase())
.build();
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}

How to disable Spring boots default error handling?

I already tried disabling the Default Error handling of Spring boot w/c throws
{
"timestamp": 1575346220347,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.web.client.HttpClientErrorException",
"message": "401 Unauthorized",
"path": "/auth/login" }
By adding the ff. Config.
#SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class)
and
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
But I'm getting a bunch of HTML formatted Response instead of the JSON response it should be getting from the server.
You can Use Controller Advice to make a global exception handler. Inside the ControllerAdvice class, you can use #ExceptionHandler annotation to handle exceptions. Here is a good article about ControllerAdvice. https://medium.com/#jovannypcg/understanding-springs-controlleradvice-cd96a364033f
I was not able to disable SpringBoots automatic handling of Error responses however I was able to get the proper JSON Error Response by wrapping my Rest Template request in a try catch and using a library in the rest template as it turns out there is a bug in Rest Template that wouldn't allow me to retrieve the Response body.
From
private final RestTemplate restTemplate = new RestTemplate();
To
private final RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
Try-Catch Wrapping
ResponseEntity resp = null;
try{
resp = restTemplate.postForEntity(hostUrl+loginUrl, request,Object.class);
}catch(HttpClientErrorException e) {
ErrorDto result = new ObjectMapper().readValue(e.getResponseBodyAsString(), ErrorDto.class);
return new ResponseEntity<>(result, e.getStatusCode());
}
ErrorDto.java
#JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorDto {
#JsonProperty("Message")
private String message;
#JsonProperty("Reason")
private String reason;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
}

Change Spring Boots default JSON error response structure

I have an API built with Spring Boot. By default the default JSON structure when an error is thrown by Spring is;
{
"timestamp": 1477425179601,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/categoriess"
}
This structure is different to error responses returning myself in the API, so I'd like to change Spring to use the same structure as my own for consistency.
My error response are structured like this;
{
"errors": [
{
"code": 999404,
"message": "The resource you were looking for could not be found"
}
]
}
How would I go about doing this? I've tried using an Exception Handler, but I can't figure out the correct exception to set it up for. I'd like to also make sure that the Http status is still correctly returned as 404, or whatever the error is (500 etc).
I had another look at this and did manage to put something together that works for me.
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
Map<String, Object> error = new HashMap<>();
error.put("code", errorAttributes.get("status"));
error.put("message", errorAttributes.get("error"));
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("errors", error);
return errorResponse;
}
};
}
This returns the following JSON response along with whatever header/http status code spring was going to return.
{
"errors": {
"code": 404,
"message": "Not Found"
}
}
This seems to work great for errors generated by spring, while my own Exceptions I'm handling in Controllers or in a specific ControllerAdmin class with ExceptionHandlers.
A possible way to do something like this is to use the #ExceptionHandler annotation to create a handler method inside your controller.
#RestController
#RequestMapping(produces = APPLICATION_JSON_VALUE)
public class MyController {
#RequestMapping(value = "/find", method = GET)
public Object find() {
throw new UnsupportedOperationException("Not implemented yet!");
}
#ExceptionHandler
public ErrorListModel handleException(Exception exception) {
ExceptionModel exceptionModel = new ExceptionModel(1337, exception.getMessage());
ErrorListModel list = new ErrorListModel();
list.add(exceptionModel);
return list;
}
private class ErrorListModel {
private List<ExceptionModel> errors = new ArrayList<>();
public void add(ExceptionModel exception) {
errors.add(exception);
}
public List<ExceptionModel> getErrors() {
return errors;
}
}
private class ExceptionModel {
private int code;
private String message;
public ExceptionModel(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}
The private classes ErrorListModel and ExceptionModel just help defining how the resulting JSON body should look, and I assume you already have your own, similar classes.
The find method just throws an exception for us to handle, which gets intercepted by the handleException method because it's annotated with #ExceptionHandler. In here, we create an ExceptionModel, populate it with information from the original exception, and add it to an ErrorListModel, which we then return.
This blog post from 2013 explains the features better than I ever could, and it also mentions an additional option, #ControllerAdvice. It basically allows you to re-use the exception handling in other controllers as well.

Disable redirect to /error for certain urls

I have created a springboot application that contains some Rest API endpoints in .../api/myEndpoints... and thymeleaf templates for some UI forms the user can interact with.
Since I added an errorController:
#Controller
#RequestMapping("/error")
public class ErrorController {
#RequestMapping(method = RequestMethod.GET)
public String index(Model model) {
return "error";
}
}
whenever an exception is being thrown in my RestControllers, I receive an empty white website containing the word "error". This maybe makes sense for the web frontend, but not for my api. For the API I want spring to output the standard JSON result e.g.:
{
"timestamp": 1473148776095,
"status": 400,
"error": "Bad request",
"exception": "java.lang.IllegalArgumentException",
"message": "A required parameter is missing (IllegalArgumentException)",
"path": "/api/greet"
}
When I remove the index method from the ErrorController, then I always receive the JSON output.
My question is: Is it somehow possible to exclude the automatic redirection to /error for all api urls (../api/*) only?
Thanks a lot.
There may be a better solution out there, until then... here's how you can achieve what you asked:
(1) Disable ErrorMvcAutoConfiguration
Add this to your application.properties:
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
(2) Define two ControllerAdvices
Since we disabled ErrorMvcAutoConfiguration, we need to catch the exception ourself. Create one advice to catch error for a specific package, and another advice to catch all other. They each redirect to a different url.
//Catch exception for API.
#ControllerAdvice(basePackageClasses = YourApiController.class)
#Order(Ordered.HIGHEST_PRECEDENCE)
public static class ErrorApiAdvice {
#ExceptionHandler(Throwable.class)
public String catchApiExceptions(Throwable e) {
return "/error/api";
}
}
//Catch all other exceptions
#ControllerAdvice
#Order(Ordered.LOWEST_PRECEDENCE)
public static class ErrorAdvice {
#ExceptionHandler(Throwable.class)
public String catchOtherExceptions() {
return "/error";
}
}
(3) create a controller to handle the error page
This is where you can have different logic in your error handling:
#RestController
public class MyErrorController {
#RequestMapping("/error/api")
public String name(Throwable e) {
return "api error";
}
#RequestMapping("/error")
public String error() {
return "error";
}
}
With Spring-Boot 1.4.x you can also implement ErrorViewResolver (see this doc):
#Component
public class MyErrorViewResolver implements ErrorViewResolver {
#Override
public ModelAndView resolveErrorView(HttpServletRequest request,
HttpStatus status, Map<String, Object> model) {
if("/one".equals(model.get("path"))){
return new ModelAndView("/errorpage/api");
}else{
return new ModelAndView("/errorpage");
}
}
}

Resources