Keywords: Spring Exception Handling | Filter Exceptions | ControllerAdvice | HandlerExceptionResolver | Unified Error Response
Abstract: This paper provides an in-depth analysis of exception handling challenges in Spring application filters and presents two robust solutions. It examines why @ControllerAdvice fails to catch filter exceptions and details the implementation of custom exception handling filters and HandlerExceptionResolver integration. Through complete code examples and configuration guidelines, the study demonstrates how to achieve unified 5xx error JSON responses, ensuring user-friendly presentation of server-side errors like database connection failures. The research also compares XML and Java configuration approaches, offering comprehensive technical guidance for developers.
Problem Background and Challenges
In modern Spring application development, exception handling is crucial for ensuring system stability and user experience. Many developers commonly use the @ControllerAdvice annotation to uniformly handle exceptions at the controller layer, effectively converting technical exceptions into user-friendly error responses. However, the situation becomes significantly more complex when exceptions occur at the filter layer.
Specifically, when database connections are interrupted, Spring throws CannotGetJdbcConnectionException. In controller methods, such exceptions are normally caught and processed by @ControllerAdvice. But when the same exception occurs in custom filters (such as CORS filters extending OncePerRequestFilter), the @ControllerAdvice mechanism completely fails, resulting in users seeing raw stack traces instead of formatted error responses.
Core Solution Analysis
Through extensive research and practical validation, we have identified two effective solutions based on different design philosophies and technical implementations.
Solution 1: Custom Exception Handling Filter
The core concept of this approach involves inserting a dedicated exception handling filter at the very beginning of the filter chain, using try-catch mechanisms to capture all runtime exceptions and manually construct JSON responses. Here is the specific implementation code:
public class ExceptionHandlerFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (RuntimeException e) {
ErrorResponse errorResponse = new ErrorResponse(e);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.getWriter().write(convertObjectToJson(errorResponse));
}
}
public String convertObjectToJson(Object object) throws JsonProcessingException {
if (object == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(object);
}
}
In XML configuration, ensure this filter precedes all other filters:
<filter>
<filter-name>exceptionHandlerFilter</filter-name>
<filter-class>xx.xxxxxx.xxxxx.api.controllers.filters.ExceptionHandlerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionHandlerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Solution 2: HandlerExceptionResolver Integration
This approach is more elegant, achieving unified exception handling by reusing existing exception handling mechanisms. First, define a standard exception handling class:
@RestControllerAdvice
public class ExceptionTranslator {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorDTO processRuntimeException(RuntimeException e) {
return createErrorDTO(HttpStatus.INTERNAL_SERVER_ERROR,
"An internal server error occurred.", e);
}
private ErrorDTO createErrorDTO(HttpStatus status, String message, Exception e) {
// Specific error DTO construction logic
}
}
Then create a filter that integrates HandlerExceptionResolver:
@Component
public class FilterChainExceptionHandler extends OncePerRequestFilter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
resolver.resolveException(request, response, null, e);
}
}
}
Register the filter in security configuration:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private FilterChainExceptionHandler filterChainExceptionHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
// Other security configurations
}
}
Technical Implementation Details
During implementation, several key technical details require special attention:
Importance of Filter Execution Order
The exception handling filter must execute at the very beginning of the filter chain to ensure capture of all potential exceptions from subsequent filters. In Spring Security's default filter chain, it's recommended to place the exception handling filter before LogoutFilter to cover most security-related exceptions.
Unified Error Response Format
Regardless of the chosen solution, ensure consistent error response formats throughout the application. We recommend defining a unified error response class:
public class ErrorResponse {
private String message;
private String error;
private int status;
private String path;
private LocalDateTime timestamp;
// Constructors, getters, and setters
}
Exception Type Handling Scope
In practical applications, beyond RuntimeException, consider handling other exception types. Based on specific business requirements, extend exception handling to include:
SQLExceptionand its subclassesIOException- Custom business exceptions
Solution Comparison and Selection Recommendations
Both solutions have their advantages and disadvantages. Developers can choose based on specific requirements:
Advantages of Custom Filter Solution
- Simple implementation with clear logic
- No dependency on Spring's complex exception handling mechanisms
- Suitable for simple exception handling requirements
Advantages of HandlerExceptionResolver Integration
- High code reusability, seamless integration with existing
@ControllerAdvicemechanisms - Support for more complex exception handling logic
- Alignment with Spring design philosophy
Best Practice Recommendations
Based on practical project experience, we summarize the following best practices:
Importance of Logging
During exception handling, detailed logging of exception information—including exception type, stack traces, request parameters, and other critical data—is essential for subsequent problem troubleshooting and system monitoring.
Security Considerations
In production environments, avoid exposing sensitive information in error responses, such as database connection strings or internal server paths. Implement appropriate data masking for error messages.
Performance Optimization
Exception handling should not become a system performance bottleneck. Recommendations include:
- Avoid complex computations during exception handling
- Use connection pooling to reduce database connection exceptions
- Implement caching for frequently occurring exceptions
Conclusion
Exception handling in Spring framework filters represents a common yet often overlooked technical challenge. Through the two solutions presented in this paper, developers can effectively address the limitation of @ControllerAdvice in capturing filter exceptions. Whether choosing the straightforward custom filter approach or the more elegant HandlerExceptionResolver integration method, both enable unified error response handling, enhancing system stability and user experience.
In practical project development, we recommend selecting the appropriate solution based on project complexity and team skill level. For small to medium-sized projects, the custom filter approach may be more suitable; for large, complex projects, the HandlerExceptionResolver integration provides better scalability and maintainability.