How to throw and detect this exception correct - spring

I'm using #ControllerAdvice to detect exceptions that are thrown in the application.
Trying to throw exception during creation of a class:
public void setStatus(String status) throws InvalidInputStatusException{
if(checkIfStatusIsAllowed(status)) {
this.status = status;
} else {
throw new InvalidInputStatusException();
}
}
Trying to catch the error:
#ControllerAdvice
public class RekvisisjonRESTControllerExceptionHandler {
//TODO: Add logger here!
#ExceptionHandler
public final ResponseEntity<RekvisisjonRESTErrorResponse> handleException(InvalidInputStatusException e, WebRequest request) {
//TODO: Do some logging
return new ResponseEntity<>(new RekvisisjonRESTErrorResponse(HttpStatus.BAD_REQUEST.toString(),
e.getClass().getName(),
e.getMessage(), LocalDateTime.now()), HttpStatus.BAD_REQUEST);
}
}
What I want is the object specified above returned, but I get this crap here instead:
"error": "Bad Request",
"message": "JSON parse error: Ugyldig status som input; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Ugyldig status som input\n at [Source: (PushbackInputStream); line: 2, column: 12] (through reference chain: no.pasientreiser.atom.rekvisisjon.controller.dto.UpdateRekvisisjonStatusRequest[\"status\"])",
"trace": "org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Ugyldig status som input; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Ugyldig status som input\n at [Source: (PushbackInputStream); line: 2, column: 12] (through reference chain: no.pasientreiser.atom.rekvisisjon.controller.dto.UpdateRekvisisjonStatusRequest[\"status\"])\n\tat org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:245)\n\tat org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:227)\n\tat org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:205)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:158)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:131)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\n\tat org.springframework.web.me
I'm assuming it fails to detect the intended exception because another is thrown before it, but this is not what i want.
Any recommendations?

An exception handler handles exceptions that occur in your handler methods (see https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc). The exception you see happens earlier, while Spring is trying to turn the JSON request body into an UpdateRekvisisjonStatusRequest. The Jackson JSON deserializer is invoking the setStatus method and encounters an exception, which Spring takes to mean the HTTP body is not readable (since Jackson couldn't deserialize it).
Take a look at how Spring MVC handles validation instead: https://spring.io/guides/gs/validating-form-input/

The exception happens not in your business code, but during the parsing of the request into your request presentation object. Spring Web treats any kind of exception that happened during parsing of the request as a presentation-level error, not a business-level error, hence, your exception handler doesn't get invoked.
Since you try to enforce a business rule here, I'd propose to move it out of the setter method of a presentation object and find a better place for it. E.g. put this logic inside the business entity, or one of your services, or at the very least in the controller method that accepts the request.

First your RekvisisjonRESTControllerExceptionHandler should extends from ResponseEntityExceptionHandler.
If you return ResponseEntity, it would wrap your value class (RekvisisjonRESTErrorResponse).
Here your exception is generated after the advice, when json serialized.

Related

How to check Bad request for #Min on request param in spring controller?

I am pretty new to spring controller. I am trying to write unit test for invalid parameter. I have an api that has #RequestParam("id") #Min(1) long id and in my unit test, I pass in "-1". Here is my test:
#Test
public void searchWithInvalidIbId() throws Exception {
mockMvc.perform(get(BASE_URL)
.param(COLUMN_IB_ID, INVALID_IB_ID_VALUE) // = "-1"
.param(COLUMN_TIME_RANGE, TIME_RANGE_VALUE)
.param(COLUMN_TIME_ZONE, TIME_ZONE_VALUE)
.accept(PowerShareMediaType.PSH_DISPATCH_REPORTER_V1_JSON)
.contentType(PowerShareMediaType.PSH_DISPATCH_REPORTER_V1_JSON))
.andExpect(status().isBadRequest());
}
When I run this, I get
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.ConstraintViolationException: search.arg2: must be greater than or equal to 1
It makes sense, but I am not sure how to test this is BadRequest. I tried #Test(expected = NestedServletException.class), and it passed, but I don't think it is checking what I want to check. What is the right approach to check this?
You can have your custom exception handler annotated with #ControllerAdvice and handle ConstraintViolationException in that class. You can throw your custom exception with additional details if you wish.
Here is an example approach:
#ControllerAdvice
public class MyCustomExceptionHandler {
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(ConstraintViolationException.class)
ApiError constraintViolationException(ConstraintViolationException e) {
return BAD_REQUEST.apply(e.getBindingResult());
}
}
Here ApiError is a custom class to represent your error response, it can be anything else you want. You can add timestamp, http status, your error message etc.

#ExceptionHandler is Not working when automatic binding fails in REST API

I have two REST API's GET POST
When any Exception is thrown inside the method, Exception handler is working fine.
But if i use malformed REST api uri then it only shows 400 Bad Request without going to Exception Handler.
Eg.
If I hit http://localhost:8080/mypojoInteger/abc, it fails to parse string into Integer and hence I am expecting it to go to ExceptionHandler.
It does not go to Exception Handler, Instead I only see 400 Bad Request.
It works fine and goes to Exception Handler when any Exception is thrown inside the GET/POST method.
For eg: It works fine and goes to Exception Handler if I use 123 in path variable
http://localhost:8085/mypojoInteger/123
And change getData method to
#GetMapping("/mypojoInteger/{sentNumber}")
public void getData(#PathVariable("sentNumber") Integer sentNumber) {
throw new NumberFormatException("Exception");
}
NOTE: Same issue is with POST request also.
GET:
#GetMapping("/mypojoInteger/{sentNumber}")
public void getData(#PathVariable("sentNumber") Integer sentNumber) {
//some code
}
POST:
public void postData(#RequestBody MyPojo myPojo) {
//some code
}
Controller Advice class:
#ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(NumberFormatException.class)
protected ResponseEntity<Object> handleEntityNotFound(
NumberFormatException ex) {
// some logic
}
}
How can I handle Exception when it fails to bind String to Integer in REST API uri itself??
EDIT: My Requirement is I should handle the overflow value of integer i.e, If a pass more than maximum value of Integer it must handle it rather than throwing NumberFormatException Stack Trace.
Eg: When i pass over flow value
POJO:
public class MyPojo extends Exception {
private String name;
private Integer myInt;
//getters/setter
}
{
"name":"name",
"myInt":12378977977987879
}
Without #ControllerAdvice it just shows the NumberFormatException StackTrace.
With #ControllerAdvice it just shows 400 bad request with no Response Entity.
I do not want this default stacktrace/400 bad request in case of this scenario
but I want to show my custom message.
The reason that i see is that, because since your request itself is malformed-> the method body never gets executed - hence the exception never occurs because it is only meant to handle the error within the method . It is probably a better design choice for you to form a proper request body rather than allowing it to execute any method so you know the problem before hand.
The issue is because Integer object is not sent as a valid request parameter, example of request: 5 if you send String an exception will be thrown directly. If you want to check if it is a String or Integer you might change your code by following this way:
#GetMapping("/mypojoInteger/{sentNumber}")
public void getData(#PathVariable("sentNumber") Object sentNumber) {
if (!(data instanceof Integer)) {
throw new NumberFormatException("Exception");
}
}
This should work on your example.
Solution:
I found out that I need to handle Bad Request.
So, I have override
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
//Handle Bad Request
}

Spring boot exception handler : throw an exception

Using this works fine
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ExceptionHandler(value = IoTException.class)
public void IoTError() {
}
But when I try to convert to another homemade exception
#ExceptionHandler(value = IoTException.class)
public void IoTError() {
throw new IoTConnectionException();
}
Exception handler is ignored, i.e. IoTException are sent to the view without being converted to IoTConnectionException. But placing a breakpoint showed me enter the IoTError method.
Any idea why ? Thanks :)
The docs about exception handling state:
If an exception occurs during request mapping or is thrown from a request handler such as an #Controller, the DispatcherServlet delegates to a chain of HandlerExceptionResolver beans to resolve the exception and provide alternative handling, which typically is an error response.
At the point where you are throwing the IoT exception, the delegation to the chain of HandlerExceptionResolver has already been taken place and it will not be executed again. If this exception would trigger another exception handling dispatch it could cause exception cycles. Imagine you would have defined another exception handler method for IoTConnectionException and this would throw IoTException. You would end with a StackOverflowException.
In this section Docs Exception handler methods all supported return values of an exception handler method are described.

#ControllerAdvice and Create Error Message in Business Layer

Assume i have defined #ControllerAdvice
#ControllerAdvice
public class ErrorHandler {
// example
#ExceptionHandler(ParseException.class)
public void handleParseException(ParseException exception, HttpServletResponse response) {
// return response - error message, error description, error type (ERROR or EXCEPTION)
}
}
The problem is where i will formulate the error message text with parameters and also the message type - ERROR or EXCEPTION
Example, Assume file not found exception is thrown for the given file name
Generally, in message.properties file we will be having text as File Doesn't Exist for file {0}
The translation of error message usually happens in the presentation layer.....
Now if we need to pass the error message so that controller advice takes care of passing it to the UI....
Do we need to construct the error message in the service layer before sending ??? Where the exception and the parameters will be binded ???
for example
public void accessFile(String fileName) {
File file = new File(fileName);
if(!file.exists()) {
throw new FileNotFoundException(Constants.FILE_NOT_FOUND.....);
How to construct the error message with property key and sending with
proper error message binded with exception???
so that in controller advice we would just use exception.getMessage()
which will have the translated text.
}
}
Please let me know how to do it.
I would have a method like below to create a json object and use the same in UI to populate error message.
#RestControllerAdvice
public class ExceptionProcessor {
//.....
//.....
#ExceptionHandler(IOException.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResource handleParseException(final IOException ex, final WebRequest request) {
// return response - error message, error description, error type (ERROR or EXCEPTION)
return _errorBuilder.build(ex, GenericErrorCode.ERR0500, request)
.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value())// error code
.setMessage("Internal server error") //message
.setDetails(new ErrorDetailResource.Builder()
.setLocationType("system")
.setMessage(ex.getMessage())// description
.build())
.build();
}
So basically you are creating an object from your exception and passing it back as your response so that UI can handle this error message appropriately.
Now you can design your object to pass it to UI
and you can build your error message in business layer while throwing excpetion or read it from property as given below.
#Value("${error.invalidfile.message}")
private String parseErrMessage;
and then in that method when you are creating a message, use this message
---------Edit 2
If you need to pass something other than excpetion message then create your own exception.
MyIOException extends IOException{
//...
String exceptionMessagKey;
//getter and setter
}
Then throw and catch this exception and build your message accordinggly from message and the exceptionMessagKey in the excpetion object.
public ErrorResource handleParseException(final MyIOException ex, final WebRequest request){
...
// Use ex.getExceptionMessagKey() and ex.getMessage()
....
}

How to handle HttpMediaTypeNotAcceptableException by writing error content to the response body using exception handler annotation?

When a client request for a resource producing application/json content with Accept Header of application/xml. The request fails with HttpMediaTypeNotAcceptableException exception and is wrapped into error message body in the response entity object by using exception handler annotation as mentioned in below code. However, we receive HttpMediaTypeNotAcceptableException again when return values are written to the response with HttpMessageConverter. It is because it checks the producible content type for the response with the acceptable request type, but this is exactly something we are trying to communicate to the client using error message. How do I workaround this issue ? Btw, all the other exceptions are parsing fine to error message. Please advise.
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body,
HttpHeaders headers, HttpStatus status, WebRequest request) {
// Setting the response content type to json
headers.setContentType(MediaType.APPLICATION_JSON);
return ResponseEntity.status(status).headers(headers).body(body);
}
}
A few options come to my mind. One is that your controller method produces all content types and then you throw an exception in your method if the content type is not the one you are expecting, then the exception handler can take this exception and transform it. This is the only one that works with exception handlers, as exception handlers only deal with exceptions produced in the controller method.
The other options are:
Use an interceptor (but I'm not sure if this will work, as Spring might try to resolve first the controller method rather than invoking the interceptors).
Extend RequestMappingHandlerMapping to call the exception handler if it doesn't find a suitable method. You'll probably need to override the method handleNoMatch. In there you'll need to get a reference to the list of HandlerExceptionResolver
The first one is the simplest to understand, and the latest one might be the most 'extensible', but it also requires some understanding of the internals of Spring.
Resolved by setting different content negotiation strategy FixedContentNegotiationStrategy for ExceptionHandlerExceptionResolver and HeaderContentNegotiationStrategy for RequestResponseBodyMethodProcessor.
I have been using a serialized enum-based response (enum annotated with jackson #JsonFormat(shape = Shape.OBJECT) to standardize the error messages in my exception handler class and faced the same issue when it caught with a HttpMediaTypeNotAcceptableException.
The workaround is to set the media type you expect to return directly to the builder method available in the ResponseEntity.
The below code works fine for me.
#ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public final ResponseEntity<ResponseMessagesEnum> handleHttpMediaTypeNotAcceptableException(
HttpMediaTypeNotAcceptableException e, HttpServletRequest request) {
logger.error("No acceptable representation found for [{}] | supported {}", request.getHeader("Accept"), e.getSupportedMediaTypes());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
.body(ResponseMessagesEnum.EX_001);
}

Resources