I have a controller advice that handle all validation exception thrown by my application as below.
#RestControllerAdvice
public class RestApiExceptionController {
#ExceptionHandler(ValidationErrorException.class)
public ResponseEntity<?> appNotFoundException(ValidationErrorException exception) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse(exception.getErrorCode(), exception.getMessage()));
}
}
In my way, I would like to create a filter that will make validation to every request and throw custom exception when necessary. But the thing is that I cannot throw custom exception as show below.
public class ValidationFilter implements Filter {
...
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
throw new ValidationErrorException(); // This is impossible
}
...
}
How can I throw the ValidationErrorException in this case or there are other better ways to handle such task.
The validations are generally done on the request objects which are in general available in Controller layer after they are transformed from request format to server processing format. e.g. JSON to Java object.
So the validation should be performed or triggered on Controller layer once the request is reached by completing your entire filter chaining.
Any validation exception thrown then later on can be handled in your following handler,
#RestControllerAdvice
public class RestApiExceptionController {
#ExceptionHandler(ValidationErrorException.class)
public ResponseEntity<?> appNotFoundException(ValidationErrorException exception) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse(exception.getErrorCode(), exception.getMessage()));
}
}
The very much one of the purpose of filters is,
To intercept requests from a client before they access a resource at
back end.
So filters in real don't have the actual resource yet to be validated. They are available once the control reaches to correct component and in your case it is Controller.
So better approach is not to do any resource based validations at filter components.
Related
I have a class annotated with #ControllerAdvice and methods annotated with #ExceptionHandler to handle exceptions thrown by the service code. When handling these exceptions, I would like to get the #RequestBody that was part of the request POST operations.
I tried by creating POJO class for the request sent and tried to retrieve inside exception handler class but it returns null.
Followed the same solution given in this query~ How to get the #RequestBody in an #ExceptionHandler (Spring REST)
It doesn't work.
I also tried with httpServertRequest, even that return null for the request data.
I want to get the request body of the api inside ExceptionHandler method. Based on the request body data I have to return the error response.
I also want the query param data to be used inside ExceptionHandler method.
Please provide with a solution. Thank you.
The Stream of Request Body only read once, so you can't retrieve it for the second time inside ControllerAdvice, thus cache the request body with ContentCachingRequestWrapper first. Create a new Spring component like this:
#Component
public class FilterConfig extends GenericFilterBean{
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
HttpServletRequest request = (HttpServletRequest) servletRequest;
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request);
filterChain.doFilter(wrapper, servletResponse);
}
}
then in #ControllerAdvice make your #ExceptionHandler accept ContentCachingRequestWrapper as parameter.
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleDefaultException(Exception ex, ContentCachingRequestWrapper request){
String reqBody = "your request body is " + new String(request.getContentAsByteArray(), StandardCharsets.UTF_8);
// then do another thing you wanted for exception handling
}
I have a series of Rest API Controllers in my Spring boot application with Request Mappings that match certain URLs.
I need to change my implementation to always make sure that a specific custom header is in place for all requests. If header is not there I want to fail the request. If it is I want to forward to the appropriate controller which would be the same as my current implementation.
Is there a way to do this in Spring Boot without modifying my existing controllers at all? Could I try to use something like Spring Security, even though my header is not related to security at all?
Thank you.
Web MVC defines an abstraction called "HandlerInterceptor" and its no-op implementation HandlerInterceptorAdapter
So you can register the bean that looks like this:
#Component
public class RequestProcessingTimeInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// check the headers, extract them from request, whatever
return true; // if you want to proceed to controller
return false;// otherwise :)
}
}
This will instruct spring mvc to call the method before the flow gets to the controller.
You can configure a Filter as a #Service.
#Service
#NoArgsConstructor #Log4j2
public class FilterImpl implements Filter {
#Override
public void init(FilterConfig config) throws ServletException { }
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request.getHeader("required-header-name") != null) {
chain.doFilter(request, response);
} else {
log.info("Rejected {}", request);
}
}
#Override
public void destroy() {
}
}
I am trying to perform some common logic that applies to all my #ExceptionHandlers in code. I know I can write a HandlerInterceptor to intercept happy paths. But I would like to hook into the exception handling lifecycle so that I can execute some common logic such as logging, before the error response is rendered.
Is there anyway to do this in Spring Boot / Spring MVC? I would like to avoid having to write a servlet filter for this purpose if possible.
I have a solution. It's about using HandlerInterceptor.afterCompletion method. However, there is a line in the documentation of this method that states that:
Note: Will only be called if this interceptor's preHandle method has successfully completed and returned true!
So the trick is to also implement the preHandle and make it return true.
Now my interceptor looks like this:
#Component
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// perform common logic here
}
}
One thing to be aware of though is that if you have a chain of interceptors and an interceptor before this one throws an exception, this interceptor won't get a chance to execute. So if we reorder the interceptor chain so that MyInterceptor is right at the top, it will intercept all requests.
There is a way with #RestControllerAdvice and #ExceptionHandler, an example:
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(value = {DeniedPermissionException.class})
#ResponseStatus(HttpStatus.FORBIDDEN)
public String deniedPermissionException(DeniedPermissionException ex) {
return "Denied permission";
}
#ExceptionHandler(value = {ConstraintViolationException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
public String constraintViolationException(ConstraintViolationException ex) {
return "Bad request";
}
#ExceptionHandler(value = {Exception.class})
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String internalServerError(Exception ex) {
return "Internal error";
}
}
*DeniedPermissionException is a custom exception.
This is not really a Spring Boot concern. This is really a Spring MVC concern. One good approach is to implement the HandlerExceptionResolver or extend from something like ExceptionHandlerExceptionResolver. The implementation needs to be given a higher precedence than the default exception resolvers (which all run with the lowest precedence). And if you want to retain the existing behavior of the default resolvers but only trap the exception for something cross-cutting like logging or tracking, then just return null for the ModelAndView and Spring will ensure other default resolvers are invoked as before.
I am writing one REST api. There might be two exceptions in my DAO layer namely Exception_X and Exception_Y. If I encountered a exception Exception_X in DAO layer, my controller should return status code 200, if Exception_Y then 401 and if all goes well controller should return 201.
Now what was I thinking that I will throw encountered exception as it is from DAO layer to controller via service layer and in catch block of controller I will return response.
Is it acceptable or there is some other standard way?
Yes that is quite an acceptable way. However, rather than using try-catch, I would suggest to implement Exception Handlers for your REST Controllers. That way, you won't have to clutter your REST methods.
Also, it would be better to create a model object in REST layer for Error messages - ErrorResponse, with appropriate information:
class ErrorResponse {
int statusCode;
String errorMessage;
}
And return it's object from the exception handlers. BTW, you can also map your exception class directly to a response using #ResponseStatus annotation:
#ResponseStatus(value=401, reason="message")
class Exception_Y extends RuntimeException {
}
Then you don't have to write exception handler for that exception.
My suggestion would be wrap any unchecked exceptions with a service layer for loose coupling, and clean abstraction. Keep your controller free from conditions and let Service layer take care of this pain.
Keeping security concern in mind if you exposing it externally wrap your exception with service oriented exception it also helps to achieve generic layer specific exceptions say PersistentException, ServiceException etc. keeping good degree of decoupling in mind.
For handling exception globally you can use spring inbuild ControllerAdvice annotation with JsonExceptionModel for formatted error response.
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(SQLException.class)
public Map<String, Object> handleSQLException(HttpServletRequest request, Exception ex) {
//json response here
}
}
public class JsonExceptionModel {
private int code;
private String type;
private String url;
private String message;
private String moreInfo;
// getters/setters here
}
I suggest you to go with Exception Resolver which is providing by spring.
Spring Framework provides HandlerExceptionResolver interface that we can implement to create global exception handler. We can also override it to create our own global handler with our application specific changes, such as logging of exception messages.
Here is the sample implementation of HandlerExceptionResolver,which will fullfill your need
public class RestResponseStatusExceptionResolver extends HandlerExceptionResolver {
#Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
if (ex instanceof InvalidInputException) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return handleException(ex);
} else if (ex instanceof ResourceNotFoundException) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return handleException(ex);
}
//Adding error details to modelView object
modelAndView.addObject("errors", ErrorDetails);
// Custom error message details
public class ErrorDetails {
private String code;
private List<String> data;
}
What is the significance of return type in spring controllers which are used for download. Please consider the following use case:
public ModelAndView execute(final HttpServletRequest request, final HttpServletResponse response) {
try {
//some code.
} catch {
//handle the exception and build a error model and view. This model and view
//gives a lot of freedom for error handling in case of download fails on the
//same page without change in URL(enabling refresh of the same page again
//and again)
return modelAndView;
}
return null;
}
but generally I have seen controllers which has void return types which would look like the one below
public void execute(final HttpServletRequest request, final HttpServletResponse response) {
try {
//some code.
} catch {
//handle the exception but you cannot display the error with out leaving the same page. Error embedding is not possible without changing the URL.
}
}
I have two question here:
a) Are their any disadvantages of one approach over other. I see first serves more use cases than second.
b)Is there any disadvantage of returning null instead of ModelAndView.
References:
Downloading a file from spring controllers
Error handling by redirection in spring download file controller
Nothing bad as for marking method as void. You are handling download action via HttpServletResponse.
There are suggestions that FileSystemResource is cleaner but take into account that for e.g. there are cases that you need to forward your data to some other place in order to compose the report in the other place.
Also Spring lets you easily handle exceptions even when your return type in the controller is void:
#RequestMapping(value = "/pdf-report/{id}.pdf", method = RequestMethod.GET)
public void downloadPdfReport(#PathVariable String id, HttpServletRequest req, HttpServletResponse resp) throws Exception {
//supposed logic here
//if we are failing here then
throw new UserFriendlyException("Cannot produce data");
}
Then ControllerAdvice plays its role:
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler(UserFriendlyException.class)
public ModelAndView handleUserFriendlyException(UserFriendlyException ex) {
//handle here your custom error page
}
}
More info on that from the Spring resources