Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot custom ErrorAttributes http status not set to response

Following Spring Boot documentation I defined my own ErrorAttributes bean (see below), I was able to make the json response to show the information I wanted, including my own error code and message by using a custom exception to wrap that information and generate the error response from it. The only issue with this is that the http status of the response is not matching the one I define in the status attribute, it is not been overridden.

@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);

            Throwable error = getError(requestAttributes);

            if (error instanceof MyException) {
                MyException myException = (MyException) error;

                errorAttributes.put("errorCode", myException.getErrorCode());
                errorAttributes.put("message", myException.getMessage());
                errorAttributes.put("status", myException.getStatus());

                HttpStatus correspondentStatus = HttpStatus.valueOf(myException.getStatus());
                errorAttributes.put("error", correspondentStatus.getReasonPhrase());
            }

            return errorAttributes;
        }
    };
}

The response's http status is not matching the status in the json, for example:

HTTP/1.1 500 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 01 Mar 2017 18:48:22 GMT
{
   "timestamp": "2017-03-01T18:48:21.894+0000",
   "status": 403,
   "error": "Forbidden",
   "exception": "com.myapp.MyException",
   "message": "You are not authorized. This user doesn't exist in the db",
   "path": "/account",
   "errorCode": "00013"
}
like image 330
raspacorp Avatar asked Oct 26 '25 08:10

raspacorp


2 Answers

I found a way of setting the http status from within the logic that creates my custom ErrorAttributes bean, this way I am able to re-use the out of the box Spring Boot error response creation and update it with my custom information without the need of exception handlers and controller advices.

By adding the next line you can set the http status which overrides the current one in the requestAttributes.

requestAttributes.setAttribute("javax.servlet.error.status_code", httpStatus, 0);

Where httpStatus is the status you want to set.

Here is the full bean definition with the added line:

@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);

            Throwable error = getError(requestAttributes);

            if (error instanceof MyException) {
                MyException myException = (MyException) error;
                int httpStatus = myException.getStatus();

                errorAttributes.put("errorCode", myException.getErrorCode());
                errorAttributes.put("message", myException.getMessage());
                errorAttributes.put("status", httpStatus);

                HttpStatus correspondentStatus = HttpStatus.valueOf(httpStatus);
                errorAttributes.put("error", correspondentStatus.getReasonPhrase());
                requestAttributes.setAttribute("javax.servlet.error.status_code", httpStatus, 0);
            }

            return errorAttributes;
        }
    };
}

How did I find it? By looking at the DefaultErrorAttributes class, I found there is a method addStatus which is private, but it shows the name of the attribute that is used by the code to generate the response's http-status, that was the clue I was looking for:

private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
    Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
...

Looking more into the code I found that the getAttribute method that is being called there is actually calling the method from RequestAttributes interface:

private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
    return requestAttributes.getAttribute(name, 0);
}

Checking inside that interface I found there is also a setAttribute method. It worked.

HTTP/1.1 403 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 01 Mar 2017 20:59:33 GMT
{
   "timestamp": "2017-03-01T20:59:32.774+0000",
   "status": 403,
   "error": "Forbidden",
   "exception": "com.myapp.MyException",
   "message": "You are not authorized. This user doesn't exist in the db",
   "path": "/account",
   "errorCode": "00013"
}
like image 129
raspacorp Avatar answered Oct 28 '25 21:10

raspacorp


All you are doing is building the body of your error response, as you can see from your sample. Spring is the one handling the status code.

If you want to have full control on all parts of the response then you should use the ControllerAdvice approach as shown in their documentation:

@ControllerAdvice(basePackageClasses = FooController.class)
public class FooControllerAdvice extends ResponseEntityExceptionHandler {

   @ExceptionHandler(MyException.class)
   public ResponseEntity<Message> handleRequestErrorMyException(HttpServletRequest request, MyException myException) {
        HttpStatus status = HttpStatus.valueOf(myException.getStatus();
        return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
    }

}

With this bean in place all MyException thrown by any controller under the FooController package will be captured and processed by handleRequestErrorMyException, the response to the original request will be the one returned by this method. Just make sure in your Configuration class that this package gets scanned.

like image 22
artemisian Avatar answered Oct 28 '25 22:10

artemisian



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!