6

I am making a simple rest service that makes some http calls and aggregates data using RestTemplate.

Sometimes i get NotFound error and sometimes BadRequest errors.

I want to respond with the same status code to my client and Spring seems to have this mapping out of the box. the message is okay but the Status code is always 500 Internal Server error.

I Would like to map my status code to the one i am initially receiving

    "timestamp": "2019-07-01T17:56:04.539+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "400 Bad Request",
    "path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}

i would like it to be this way

    "timestamp": "2019-07-01T17:56:04.539+0000",
    "status": 400,
    "error": "Internal Server Error",
    "message": "400 Bad Request",
    "path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}

It throws HttpClientErrorException.BadRequest or HttpClientErrorException.NotFound

my code is a simple endpoint :

    @GetMapping("/{id}")
    public MyModel getInfo(@PathVariable String id){
        return MyService.getInfo(id);
    }
oygen
  • 467
  • 1
  • 7
  • 13

4 Answers4

3

You can create global exception handling with @ControllerAdvice annotation. Like this:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = YourExceptionTypes.class)
    protected ResponseEntity<Object> handleBusinessException(RuntimeException exception, WebRequest request) {
        return handleExceptionInternal(exception, exception.getMessage(), new HttpHeaders(), HttpStatus.NOT_ACCEPTABLE, request);
    }
}

When an exception is thrown, the handler will catch and transform it to the desired response. The original exception wont be propagated.

zlaval
  • 1,941
  • 1
  • 10
  • 11
  • thank you, where in my project should this code be? in a separate class or in my controller class? – oygen Jul 01 '19 at 18:33
  • in separate class – zlaval Jul 01 '19 at 18:35
  • 1
    That actually does not work as demanded. By using a @ControllerAdvice the whole body has to be build manually and transformed into JSON. The whole response body in the code snippet will be the message of the exception - the second parameter of "handelExceptionInternal". That body is no JSON, all the other information is missing, it's just a String with the message. So this is not an adequate solution. – Sven Döring Sep 30 '20 at 11:19
  • If you want common error format, you can create your own pojo to return the proper informations you need. The object will be serialized by objectmapper automatically. – zlaval Sep 30 '20 at 11:28
2

The accepted solution with the @ControllerAdvice is insufficient. That surely marks the response with the custom status code for the exception. It does, however, not return the wanted response body as JSON but as only simple string - the message from the exception.

To get the correct status code and the default error body the DefaultErrorAttributes can help.

@ControllerAdvice
public class PackedTemplateNotRecodableExceptionControllerAdvice extends ResponseEntityExceptionHandler {
    @Autowired
    private DefaultErrorAttributes defaultErrorAttributes;

    @ExceptionHandler(PackedTemplateNotRecodableException.class)
    public ResponseEntity<Object> handlePackedTemplateNotRecodableException(final RuntimeException exception, final WebRequest webRequest) {
        // build the default error response
        webRequest.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.BAD_REQUEST.value(), RequestAttributes.SCOPE_REQUEST);
        final Map<String, Object> errorAttributes = defaultErrorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults());

        // return the error response with the specific response code
        return handleExceptionInternal(exception, errorAttributes, new HttpHeaders(), HttpStatus.BAD_REQUEST, webRequest);
    }
}

That way you'll receive the wanted error response, e.g. something like this:

{
    "timestamp": "2019-07-01T17:56:04.539+0000",
    "status": 400,
    "error": "Internal Server Error",
    "message": "400 Bad Request",
    "path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
Sven Döring
  • 3,927
  • 2
  • 14
  • 17
  • Just a note: ErrorAttributeOptions.defaults() doesn't compile. Instead, we can use getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) – Ekaterina Jan 26 '21 at 20:00
2

I have spent a lot of time looking into this issue, including solutions from answers here, which didn't work for me (or I didn't implement correctly).

I finally got a breakthrough. Instead of throwing a generic Exception such as throw new Exception(message), I created classes that extends the Exception class for the specific exception type - with their respective HTTP error codes and message

@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class BadRequestException extends Exception{

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

In your application logic, you can now throw the Bad Request exception with a message like so throw new BadRequestException("Invalid Email"). This will result in an exception thrown thus :

{
"timestamp": "2021-03-01T17:56:04.539+0000",
"status": 400,
"error": "Bad Request",
"message": "Invalid Email",
"path": "path to controller"
}

You can now create other custom exception classes for the different exceptions you want, following the above example and changing the value parameter in the @ResponseStatus, to match the desired response code you want. e.g for a NOT FOUND exception @ResponseStatus (value = HttpStatus.NOT_FOUND), Java provides the different HTTP status codes via the HttpStatus enum.

For more context

I hope this is detailed enough and helps someone :)

Ugonna Ubaka
  • 61
  • 1
  • 4
0

Possible duplicate of Spring Resttemplate exception handling Your code needs a controller advice to handle the exceptions from the service it is calling.