I'm throwing response status exceptions in my codebase that look like this:
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Resource not found!");
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad request!");
But, it generates this default spring json error:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "Resource not found",
"instance": "/api/graphql",
"properties": null
}
I'd like to create my own exception handler because I don't want to show the fields: type, instance & properties.
I've tried creating my own exception handler for the ResponseStatusException.class here:
#ControllerAdvice
public class ResponseStatusHandler extends ResponseStatusExceptionHandler {
#Autowired
private ObjectMapper objectMapper;
#ExceptionHandler({ResponseStatusException.class})
public ResponseEntity<Object> handle(ResponseStatusException e) {
ObjectNode node = objectMapper.createObjectNode()
.put("code", e.getStatusCode().value())
.put("message", e.getMessage());
return ResponseEntity.status(e.getStatusCode())
.contentType(MediaType.APPLICATION_JSON)
.body(node);
}
}
But, it never seems to get invoked and only the default spring response is returned. Is it possible to add a custom exception handler for ResponseStatusException?
Related
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);
}
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.
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"
}
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 :)
Possible duplicate of Spring Resttemplate exception handling Your code needs a controller advice to handle the exceptions from the service it is calling.
So, I'm using spring boot web and I want to test this method:
private void testException(HotelTvApp app) {
throw new InternalServerException("Test message");
}
Returning custom exception:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerException extends RuntimeException {
public InternalServerException(String message) {
super(message);
}
}
A controller that invokes this method returns following JSON data:
{
"timestamp": 1558423837560,
"status": 500,
"error": "Internal Server Error",
"message": "Test message",
"path": "/api/v1/test"
}
I want to write a test that will check if the exception message is correct. I tried this:
def "createRoom() should return 500 if trying to create room with already existing number"() {
given:
def uriRequest = UriComponentsBuilder.fromPath("/api/v1/test")
when:
def perform = mvc.perform(get(uriRequest.toUriString()))
then:
perform.andExpect(status().isInternalServerError())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath('$.message', is("Test message")))
}
But I get this exception:
Caused by: java.lang.AssertionError: Content type not set
How can I check the exception message here?
You can explicitly set the contentType as below
#ExceptionHandler(OrgNotFoundException.class)
public ResponseEntity<String> exceptionHandler(final Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body("Error");
}
I use the following code to handle all exceptions of type RuntimeException in class annotated with #ControllerAdvice
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<JSONObject> RuntimeExceptionHandler(RuntimeException e) throws JSONException {
JSONObject response = new JSONObject();
response.put("message", e.getMessage());
return new ResponseEntity<JSONObject>(response, HttpStatus.BAD_REQUEST);
}
It retuns the following response to the client in case of a ValidationException:
{
"timestamp": 1496377230943,
"status": 500,
"error": "Internal Server Error",
"exception": "javax.validation.ValidationException",
"message": "Name does not meet expectations",
"path": "/signup"
}
It is not what I expected. The status code is not BAD_REQUEST and the json is different from response.
It works fine if I change JSONObject to String and pass in a string message instead of a json object. I also put a break point before return statement and the response looks fine.
Note: There is another post here which:
Has no accepted answer.
Is annotating the method with #ResponseBody which I didn't.
Is not using JSONObject
A quick fix if you require a returned JSON format:
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> RuntimeExceptionHandler(RuntimeException e) {
JSONObject response = new JSONObject();
response.put("message", e.getMessage());
return new ResponseEntity<String>(response.toString(), HttpStatus.BAD_REQUEST);
}
Is it possible to return a validation annotations message in the response for bad responses? I thought this was possible but I have noticed that our projects are not given detailed bad request messages.
#NotNull(message="idField is required")
#Size(min = 1, max = 15)
private String idField;
I'd like to see "idField is required" returned if a request is made that's missing the idField. I am using jersey 2.0. What I'm seeing for a response is this...
{
"timestamp": 1490216419752,
"status": 400,
"error": "Bad Request",
"message": "Bad Request",
"path": "/api/test"
}
It looks like your Bean validation exception(ConstraintViolationException) is translated by one of your ExceptionMappers. You can register an ExceptionMapper for ConstraintViolationException as shown below and return data in the format you want. ConstraintViolationException has all the information you are looking for.
#Singleton
#Provider
public class ConstraintViolationMapper implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException e) {
// There can be multiple constraint Violations
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
List<String> messages = new ArrayList<>();
for (ConstraintViolation<?> violation : violations) {
messages.add(violation.getMessage()); // this is the message you are actually looking for
}
return Response.status(Status.BAD_REQUEST).entity(messages).build();
}
}
Building upon Justin response, here it is a version that uses Java 8 stream API:
#Singleton
#Provider
public class ConstraintViolationMapper implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException e) {
List<String> messages = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
return Response.status(Status.BAD_REQUEST).entity(messages).build();
}
}
After reading some blog posts about making a custom exception handler for Spring, I wrote the following class:
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = Exception.class)
#ResponseBody
public ResponseEntity<Object> exceptionHandler(Exception e) {
HashMap<String, Object> msg = new HashMap<>(2);
msg.put("error", HttpStatus.PRECONDITION_FAILED.value());
msg.put("message", "Something went wrong");
return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
}
}
The intent is to send msg in the JSON response instead of giving away the Spring exception what was thrown for whatever reason.
This class isn't working, however.
When I hit, say, and invalid endpoint for my server API, I get the default response payload:
{
"timestamp": 1449238700342,
"status": 405,
"error": "Method Not Allowed",
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
"message": "Request method 'POST' not supported",
"path": "/bad_enpoint"
}
What am I missing?
Thanks.
Your handler will not be called because you want to map Exception to your custom error response but Spring MVC most likely already has one exception handler registered for Exception class. It also has one that handles HttpRequestMethodNotSupportedException for sure.
It is not a great idea however, to overwrite entire Spring MVC exception handling/mapping anyway. You should only care about specific exceptions - ones that you define.
Please read this article for a bit more insight into Spring MVC exception handling.
You don't need to extend ResponseEntityExceptionHandler to make it work.
Setting two HttpStatuses is reaaaaaly bad idea.
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(value = Exception.class)
#ResponseBody
public ResponseEntity<String> exceptionHandler(Exception e) {
return new ResponseEntity<>("Something went wrong", HttpStatus.BAD_REQUEST);
}
}