Package/Endpoint/Controller Layer Exception Handling Design - spring-boot

So here is my requirement to handle component/package-specific exceptions. for example for package A, BAD Request should return spring default bad request-response, but for package B it should return custom response.

You can specify the behaviour for specific exceptions thrown from your code using an #ExceptionHandler. Also, you can limit your exception handlers to specific packages:
#ControllerAdvice(package = "com.example.b")
public class PackageBErrorHandler {
#ResponseStatus(BAD_REQUEST)
#ExceptionHandler
#ResponseBody
public ErrorDto handleValidationError(ConstraintValidationException e) {
return ...; //build your custom response here
}
}

Related

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

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.

Spring 5 exception handling - ResponseStatusException model

I was reading the article - https://www.baeldung.com/exception-handling-for-rest-with-spring
which says
Spring 5 introduced the ResponseStatusException class.
We can create an instance of it providing an HttpStatus and optionally
a reason and a cause:
I started implementing it , and the code is
custom exception
#ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Actor Not Found")
public class ActorNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
public ActorNotFoundException(String errorMessage) {
super(errorMessage);
}
}
method in service
public String updateActor(int index, String actorName) throws ActorNotFoundException {
if (index >= actors.size()) {
throw new ActorNotFoundException("Actor Not Found in Repsoitory");
}
actors.set(index, actorName);
return actorName;
}
controller
#GetMapping("/actor/{id}")
public String getActorName(#PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Actor Not Found", ex); //agreed it could be optional, but we may need original exception
}
}
repo:
https://github.com/eugenp/tutorials/tree/master/spring-5/src/main/java/com/baeldung/exception
Question:
why ResponseStatusException in controller again has to specify reason - "Actor Not Found" ?, as the service already said - ""Actor Not Found in Repsoitory"
What is the proper way to adapt to ResponseStatusException model?
It looks like a mistake. Ideally the service shouldn't use any HTTP code, so I would remove the annotation in ActorNotFoundException. Everything else seems fine, the exception is caught in the controller and ResponseStatusException is thrown which is good, because it's a proper layer to put HTTP stuff.
Overall it is better to use #ControllerAdvice instead of ResponseStatusException. it gives you a unified exception handling solution. Although it is not a good idea from a design point of view, ResponseStatusException can help you to avoid creating your custom exceptions and use it at the service level to throw in case of an Exception.
to avoid writing the message again you can use the message that is already available in thrown exception:
throw new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage() , ex);
for examples and more info you can refer to the following articles:
Spring Boot Exception Handling — #ControllerAdvice
Spring Boot Exception Handling — ResponseStatusException

How to pass and handle Exceptions through HTTP responses in Spring?

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.

How to handle Exception occuring when returning StreamingResponseBody from RestController

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.

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