Last Updated on 26 July 2024   |   Print Email
In this post, I’d like to share some ways to handle exceptions that may be thrown in a custom Spring filter class, with some code examples and the pros and cons of each approach. Then, you can decide which way is appropriate for your situation.You know, in Java web application development with the Spring framework, there’s often a need to handle a piece of code that can throw a checked exception. For example:
@Component
public class CustomFilterClass extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// ...
try {
// invoke code that can throw exceptions...
// continue filter chain if no exceptions thrown:
filterChain.doFilter(request, response);
} catch (ExceptionClassName ex) {
// what to do here???
}
}
}
Here are 4 ways to handle the exception in the catch block:- Log the exception and return- Re-throw the exception- Send custom response or error code- Have the exception resolved by an existing global exception handlerLet’s try method and discuss the pros and cons.
1. Log the exception and return
In this way, you can use a logger to log the exception and either break or continue the filter chain. For example, the following code logs the exception and breaks the filter chain:
try {
// invoke code that can throw exceptions...
// continue filter chain if no exceptions thrown:
filterChain.doFilter(request, response);
} catch (ExceptionClassName ex) {
LOGGER.log(ex.getMessage(), ex);
return;
}
Suppose that the LOGGERvariable is declared as follows:
private static final Logger LOGGER = LoggerFactory.getLogger(CustomFilterClass.class);
If you use the return statement in the catch block, the following will happen:- the remaining filters in the chain won’t be invoked- the request won’t reach the destination controller- a response is returned to the client immediatelyThis means that using the return statement in the catch block to skip the remaining code (if any) and break the filter chain. An exception occurrance is unusual, so it should not continue the filter chain processing.
2. Re-throw the exception
Another option when handling exceptions in a filter class is to re-throw the exception. And it depends on the signature of the current method. In the above example, the doFilterInternal() method declares that it throws ServletException and IOException, so you may need to wrap the thrown exception in an instance of these declared exceptions. For example:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// ...
try {
// invoke code that can throw exceptions...
// continue filter chain if no exceptions thrown:
filterChain.doFilter(request, response);
} catch (ExceptionClassName ex) {
throw new ServletException(ex);
}
}
In this approach, throwing a ServletException will result in sending an HTTP status code 500 Internal Server Error to the clients. Choose this method if you want to send a specific error code to the clients.
3. Send custom response or error code
If you notice, the second parameter in the doFilterInternal() method is of type HttpServletResponse. This means you can use the response object to write a custom message to the clients or send a custom error code in the catch block. The following example shows how to send a message to the clients:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// ...
try {
// invoke code that can throw exceptions...
// continue filter chain if no exceptions thrown:
filterChain.doFilter(request, response);
} catch (ExceptionClassName ex) {
response.getWriter().println("Ooops! Something wrong happened on the server");
}
}
This will send an HTTP status code 200 OK along with the specified message in the response body to the clients.Alternatively, you can choose to send a specific error code along with a message, as shown in the following code example:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// ...
try {
// invoke code that can throw exceptions...
// continue filter chain if no exceptions thrown:
filterChain.doFilter(request, response);
} catch (ExceptionClassName ex) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "You're not authorized");
}
}
In this example, the server will return an HTTP status code 401 Unauthorized along with the specified message in the response body to the clients.
4. Have the exception resolved by an existing exception handler
The last option is having the thrown exception handled by an existing exception handler. Suppose you have a global exception handler configured to handle the exception thrown in the catch block of the filter class:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ExceptionClassName.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ResponseBody
public ErrorDTO handleJwtValidationException(HttpServletRequest request, Exception ex) {
ErrorDTO error = new ErrorDTO();
error.setTimestamp(new Date());
error.setStatus(HttpStatus.UNAUTHORIZED.value());
error.addError(ex.getMessage());
error.setPath(request.getServletPath());
LOGGER.error(ex.getMessage(), ex);
return error;
}
}
The following code shows how to use this approach: let the exception handler resolve the thrown exception:
@Component
public class CustomFilterClass extends OncePerRequestFilter {
@Autowired @Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver exceptionResolver;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// ...
try {
// invoke code that can throw exceptions...
// continue filter chain if no exceptions thrown:
filterChain.doFilter(request, response);
} catch (ExceptionClassName ex) {
exceptionResolver.resolveException(request, response, null, ex);
}
}
}
You see, you need to autowire a bean of type HandlerExceptionResolver and call its resolveException() method in the catch block. By choosing this approach, you can easily reuse the existing exception handling code in your application.
Conclusion
This post has discussed four ways of handling exceptions thrown in a Spring filter class: log and return, re-throw, send a custom error code, and resolve the exception. Depending on your application’s needs, choose the approach that best suits your situation.
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.
Comments