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()
....
}
Related
Is it better to return a generic response entity like ResponseEntity<*> or to throw an exception when an error happens?
Consider the following kotlin code:
#PostMapping("/customer")
fun handleRequest(#Valid #RequestBody customer: Customer, result: BindingResult): ResponseEntity<CustomerResponse> {
if(result.hasErrors()) {
// #### Use generic response entity for return type
// #### Or throw error to be picked up in controller advice ?
}
val isValid = recapcthaService.validate(...)
if(!isValid){
// #### Use generic response entity for return type
// #### Or throw error to be picked up in controller advice ?
}
}
The request handler function returns ResponseEntity<CustomerResponse> however in the case of an error state like validation errors or recapctha validation failure I want to return a different return a ResponseEntity<ErrorResponse> as ErrorResponse is common response type for errors/exceptions.
In this case is it better to change the return type to ResponseEntity<*> or throw an exception to be picked up by controller advice?
This is more like a recommendation. Obviously both approach works fine. I would wrap ErrorResponse inside a custom ValidationeException. This way you can throw this exception from any where with error response. Also would use custom #ControllerAdvice to handle the ValidationException and map it into ResponseEntity. This can be useful for any other custom exceptions you would like to map. for example you can also move the binding result to read from MethodArgumentNotValidException.
Something like
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headera, HttpStatus status, WebREquest request) {return ResponseEntity...};
#ExceptionHandler(ValidationException.class) }
protected ResponseEntity<ErrorResponse> handleValidationErrors(ValidationExceptionex) {return ResponseEntity...};
}
For furthur explanation you can take a look here https://www.baeldung.com/global-error-handler-in-a-spring-rest-api
Agree with s7vr's opinion. It's better to throw an exception to be picked up by controller advice because you can extract the logic of constructing common errors together with this controller advice. And you can define several exceptions to separate errors. If you build with ResponseEntity<*>, you code may be like this:
if(result.hasErrors()) {
return ResponseEntity.badRequest().body(BaseRespVo.builder().code(error).data(errorDesc).build());
}
val isValid = recapcthaService.validate(...)
if(!isValid){
return ResponseEntity.badRequest().body(BaseRespVo.builder().code(invalid).data(invalidDesc).build());
}
This construct code looks bloated. So better to extract it in a common place.
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.
I have a Client and Server module in my Spring project running on separate ports. The Client module makes a POST request to the Server via a RestTemplate. The Server-Module throws a custom Exception with a custom error-message. Currently, in my Project, the Server has a RestControllerAdvice Class that handles such exceptions as follows:
#RestControllerAdvice
public class AppRestControllerAdvice {
#ExceptionHandler(ApiException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public MessageData handle(ApiException e) {
MessageData data = new MessageData();
data.setMessage(e.getMessage());
return data;
}
}
On the Client side, the following method catches the Response from the Server.
#RestControllerAdvice
public class AppRestControllerAdvice {
#ExceptionHandler(ApiException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public MessageData handle(ApiException e) {
MessageData data = new MessageData();
data.setMessage(e.getMessage());
return data;
}
#ExceptionHandler(Throwable.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public MessageData handle(Throwable e) {
MessageData data = new MessageData();
data.setMessage("UNKNOWN ERROR- " + e.getMessage());
e.printStackTrace();
return data;
}
}
Whenever the Exception is thrown on the server, here is what I receive on the Client
{
"message": "UNKNOWN ERROR- org.springframework.web.client.HttpClientErrorException: 400 Bad Request"
}
My question is, how do I retrieve the Custom Exception message that originated on the Server?
Also, why isn't the correct RestControllerAdvice module on the Client side picking up the error? (The INTERNAL_SERVER_ERROR method catches the error instead of the BAD_REQUEST method.)
My question is, how do I retrieve the Custom Exception message that originated on the Server?
To retrieve the orignal exception message you have to use dedicated ResponseErrorHandler that is capable of extracting that information, rather than using the default one (DefaultResponseErrorHandler - which I assume you use because of the message you got - org.springframework.web.client.HttpClientErrorException: 400 Bad Request).
Create:
public class CustomerResponseErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
// here you have access to the response's body which potentially contains the exception message you are interested in
// simply extract it if possible and throw an exception with that message
// in other case you can simply call `super.handlerError()` - do whatever suits you
}
}
Then use it with your RestTemplate:
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.errorHandler(new CustomerResponseErrorHandler())
.build();
}
}
Also, why isn't the correct RestControllerAdvice module on the Client side picking up the error? (The INTERNAL_SERVER_ERROR method catches the error instead of the BAD_REQUEST method.)
The correct method is executed - your RestTemplate at the moment is throwing HttpClientErrorException which is not an ApiException. It is a Throwable though.
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
}
I have implemented a Spring Rest Controller that streams back large files using the StreamingResponseBody. However, these files are coming from another system and there is the potential for something to go wrong while streaming them back. When this occurs I am throwing a custom Exception (MyException). I am handling the exception in an #ExceptionHandler implementation which is below. I am attempting to set the response httpstatus and error message but I am always receiving http status 406. What is the proper way to handle errors/exceptions while returning a StreamingResponseBody?
#ExceptionHandler(MyException.class)
public void handleParsException( MyException exception, HttpServletResponse response) throws IOException
{
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(),exception.getMessage());
}
You should handle all errors in the same way. There are many options.
I prefer next:
Controller Advice
It is a good idea to have an entity to send a generic error response, an example:
public class Error {
private String code;
private int status;
private String message;
// Getters and Setters
}
Otherwise, to handle exceptions you should create a class annotated with #ControllerAdvice and then create methods annotated with #ExceptionHandler and the exception or exceptions (it could be more than one) you want to handle. Finally return ResponseEntity<Error> with the status code you want.
public class Hanlder{
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleResourceNotFoundException(MyException
myException, HttpServletRequest request) {
Error error = new Error();
error.setStatus(HttpStatus.CONFLICT.value()); //Status you want
error.setCode("CODE");
error.setMessage(myException.getMessage());
return new ResponseEntity<>(error, null, HttpStatus.CONFLICT);
}
#ExceptionHandler({DataAccessException.class, , OtherException.class})
public ResponseEntity<?> handleResourceNotFoundException(Exception
exception, HttpServletRequest request) {
Error error = new Error();
error.setStatus(HttpStatus.INTERNAL_ERROR.value()); //Status you want
error.setCode("CODE");
error.setMessage(myException.getMessage());
return new ResponseEntity<>(error, null, HttpStatus.INTERNAL_ERROR);
}
}
Other ways:
Annotate exception directly
Other way is annotating directly the excetion with the status and the reason to return:
#ResponseStatus(value=HttpStatus.CONFLICT, reason="Error with StreamingResponseBody")
public class MyError extends RuntimeException {
// Impl ...
}
Exception Handler in a specific controller
Use a method annotated with #ExceptionHandler in a method of a #Controller to handle #RequestMapping exceptions:
#ResponseStatus(value=HttpStatus.CONFLICT,
reason="Error with StreamingResponse Body")
#ExceptionHandler(MyError.class)
public void entitiyExists() {
}
I figured the problem out. The client was only accepting the file type as an acceptable response. Therefore, when returning an error in the form of an html page I was getting httpstatus 406. I just needed to tell the client to accept html as well to display the message.