better to return generic response entity type to handle errors or to throw exception - spring

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.

Related

#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
}

Handling error scenarios in Spring REST

IMHO exceptions are for exceptional cases. Exceptions should not be thrown if the scenario can be handled without exception.
Creating exception takes at least 1ms and it comes with a performance impact. So which is the best way to handle error scenario?
Scenario#1:
ResponseEntity createOrder(#RequestBody Order order){
if(order.items == null)
return ResponseEntity.badRequest().build();
...
}
Scenario#2:
Spring provides #ControllerAdvice and ResponseEntityExceptionHandler as mentioned in Error Handling for REST with Spring
ResponseEntity createOrder(#RequestBody Order order){
if(order.items == null)
throw new CustomException();
...
}
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { CustomException.class })
protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
String bodyOfResponse = "Error";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
}
Personally i would choose scenario #2 because it's centralized. Later you would be able to change response code for that particular exception or add some verbose logging. In terms of performance scenario #1 is obviously faster, but i would neglect that time difference
Well, in the particular case you have I would use Hibernate to validate and not let the invalid data into the controller to start with.
public class Order {
...
#NotNull
#NotEmpty
private List<Integer> items;
}
It will automatically create 400 error for the case items is either empty or null (internally uses exceptions though).
To handle other exceptions I would use #ExceptionHandler as you have there, either per controller or via a #ControllerAdvice (to catch globally).
Creating exception takes at least 1ms
Exceptions are slow, but not that slow.

Can I get Spring Validation errors in prehandle

I currently have something similar to this in all of my endpoints in my spring app.
if(bindingResult.hasErrors()){
return new ResponseEntity<>(BAD_REQUEST);
}
I would like to move this to a http interceptor so that I only need it in one place. However, I cannot figure out how to get all of the errors from the binding result in preHandle.
How would I get validation errors in preHandle, or some other time before it starts the actual route?
One way to achieve what I think you're looking for is to not include BindingResult as a method parameter. Given no BindingResult is included as a method argument Spring will throw a BindException exception. You can define an ExceptionHandler, generally I've placed these within a #ControllerAdvice, to handle the exception as needed. Below is some sample code
Controller
#PostMapping
public SomeReturnObject someMethod(#Valid SomeCommand command) {
// logic - no longer contains checks for binding result errors
}
As part of ControllerAdvice
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ApplicationControllerAdvice {
....
#ExceptionHandler(BindException.class)
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
protected SomeResponse handleBindException(BindException ex) {
// handle exception
}
}

Spring Framework swallows exception of custom converters

I'm facing an issue with Spring (and kotlin?), where my global error handlers do not catch any exceptions thrown within a custom converter.
I know spring supports string->UUID mapping by default, but I wanted to explicitly check if an exception is actually thrown. Which it is the following converter. The behaviour is the same with and without my own implementation of the converter.
My WebMvcConfuguration looks as follows:
#Configuration
class WebMvcConfiguration : WebMvcConfigurerAdapter() {
override fun addFormatters(registry: FormatterRegistry) {
super.addFormatters(registry)
registry.addConverter(Converter<String, UUID> { str ->
try {
UUID.fromString(str)
} catch(e: IllegalArgumentException){
throw RuntimeException(e)
}
})
}
And this is my GlobalExceptionHandler:
(it also contains other handlers, which I ommitted for brevity)
#ControllerAdvice
class GlobalExceptionHandler : ResponseEntityExceptionHandler() {
#ExceptionHandler(Exception::class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
fun handleException(ex: Exception): ApiError {
logger.info(ex.message, ex)
return ApiError(ex.message)
}
}
And finally, the controller:
#Controller
class MyController : ApiBaseController() {
#GetMapping("/something/{id}")
fun getSomething(#PathVariable("id") id: UUID) {
throw NotImplementedError()
}
}
Exceptions inside controller (for example the NotImplementedError) methods are caught just fine. But the IllegalArgumentException thrown within the converter when invalid UUIDs are passed is swallowed, and spring returns an empty 400 response.
My question now is: How do I catch these errors and respond with a custom error message?
Thanks in advance!
I had the same problem. Spring swallowed any IllegalArgumentException (ConversionFailedException in my case).
To get the behavior i was looking for; i.e. only handling the listed exceptions and using default behavior for the other ones, you must not extend the ResponseEntityExceptionHandler.
Example:
#ControllerAdvice
public class RestResponseEntityExceptionHandler{
#ExceptionHandler(value = {NotFoundException.class})
public ResponseEntity<Object> handleNotFound(NotFoundException e, WebRequest request){
return new ResponseEntity<>(e.getMessage(), new HttpHeaders(), HttpStatus.NOT_FOUND);
}
}
I checked the solution from #georg-moser. At first, it looks good, but it looks it contains another issue. It translates all exceptions to the HTTP code of 500, which is something one not always wants.
Instead, I decided to overwrite the handleExceptionInternal method from the ResponseEntityExceptionHandler.
In my case logging the error was enough, so I ended up with the following:
#Override
#NonNull
protected ResponseEntity<Object> handleExceptionInternal(#Nonnull final Exception e,
final Object body,
final HttpHeaders headers,
final HttpStatus status,
#Nonnull final WebRequest request) {
final ResponseEntity<Object> responseEntity = super.handleExceptionInternal(e, body, headers, status, request);
logGenericException(e);
return responseEntity;
}
I hope it helps!
After some more trial and error, I have found a solution:
Instead of using #ControllerAdvice, implementing a BaseController that others inherit from and adding the exception handlers there works.
So my Base controller looks like this:
abstract class ApiBaseController{
#ExceptionHandler(Exception::class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
fun handleException(ex: Exception): ApiError {
return ApiError(ex.message)
}
}
If anyone can elaborate on why it works like this and not the other way, please do so and I will mark your answer as accepted.

Working with multiple #ControllerAdvice classes

I recently start to work with a #ControllerAdvice class to manage the exceptions in my Spring project. My current implementation is something like this:
#ControllerAdvice
public class GlobalDefaultExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) throw e;
return new ModelAndView("error/5xx", "exception", e);
}
}
My next step should be handle more exceptions, but for this I am thinking of use multiple classes with #ControllerAdvice, one for http status code. My goal is make the methods of my controller which handle the form submissions redirect the user for some of my custom status pages (I have one for each group - 1xx, 2xx, 3xx, 4xx, 5xx).
That methods have a structure similar to this:
#RequestMapping(value="cadastra")
#PreAuthorize("hasPermission(#user, 'cadastra_'+#this.this.name)")
public String cadastra(Model model) throws InstantiationException, IllegalAccessException {
model.addAttribute("command", this.entity.newInstance());
return "private/cadastrar";
}
Anyone can tell me if this is a good approach and give some hint of how implement my controller methods to accomplish what I want?
You can have multiple #ControllerAdvice classes that handle different exceptions.
However, because you are handling the Exception.class on your GlobalDefaultExceptionHandler, any exception might be swallowed by it.
The way I got around this was to add #Order( value = Ordered.LOWEST_PRECEDENCE )
on my general exception handler and #Order( value = Ordered.HIGHEST_PRECEDENCE ) on the others.
Maybe you want to define specific exception classes (thrown by your controller, for example: NoResourceFoundException or InvalidResourceStatusException and so on) so your ExceptionController can seperate the different cases and redirect them to the proper status page.

Resources