Spring MVC Interceptor Implementation and Configuration Guide
Understanding Spring MVC Interceptors
When building web applications with traditional JavaEE filters, developers often encounter a limitation. Filters execute before the Servlet layer, which means with Spring MVC's single entry point (DispatcherServlet), a filter would intercept all incoming requests indiscriminately. Spring MVC Interceptors solve this problem by providing more granular control over request handling within the Spring MVC framework itself.
Interceptor vs Filter: Key Differences
Understanding the distinction between these two components is crucial for proper architecture decisions:
| Aspect | Interceptor | Filter |
|---|---|---|
| Framework | Spring MVC specific | Servlet specification |
| Initialization | Spring IoC container | Servlet container |
| Scope | Action requests only | Nearly all requests |
| Context Access | Full access to acsion context and value stack | Cannot access action context |
| Lifecycle Calls | Multiple invocations per action | Single initialization call |
| Bean Access | Direct access to Spring beans via IoC | Requires manual service lookup |
Creating a Custom Interceptor
To implement an interceptor, extend the HandlerInterceptorAdapter class (a convenient abstract class implementing HandlerInterceptor) or directly implement the HandlerInterceptor interface.
package com.example.mvc.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
System.out.println("AuthInterceptor: Processing before controller execution");
// Example: Check for authentication token
String authToken = request.getHeader("Authorization");
if (authToken == null || authToken.isEmpty()) {
response.sendRedirect("/login");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelView) throws Exception {
System.out.println("AuthInterceptor: Processing after handler, before view rendering");
if (modelView != null) {
modelView.getModel().put("processedBy", "AuthInterceptor");
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("AuthInterceptor: Final cleanup after complete request processing");
}
}
Spring MVC Configuration
Register the interceptor in your Spring MVC configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Interceptor registration -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/api/**"/>
<mvc:mapping path="/secure/*"/>
<mvc:exclude-mapping path="/api/public/*"/>
<bean class="com.example.mvc.interceptor.AuthInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
Deep Dive into Interceptor Lifecycle Methods
The preHandle Method
This method executes before the controller's handler method is invoked. Use it for:
- Authentication and authorization checks
- Request preprocessing (encoding, parameter validation)
- Early returns to prevent unnecessary controller execution
Parameters:
request: The incoming HTTP requestresponse: The HTTP response objecthandler: The HandlerMethod object representing the target controller method
Return value semantics:
true: Allows the request to proceed to the next interceptor or handlerfalse: Blocks request processing (no subsequent interceptors or handlers execute)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle triggered for: " + request.getRequestURI());
// Check user session
HttpSession session = request.getSession(false);
UserSession userSession = (session != null) ?
(UserSession) session.getAttribute("userSession") : null;
if (userSession == null || !userSession.isAuthenticated()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"Authentication required\"}");
return false;
}
return true;
}
The postHandle Method
This method executes after the handler method completes but before the view is rendered. Use it for:
- Modifying the ModelAndView object
- Adding request attributes
- Changing view names or controlling view logic
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelView) throws Exception {
if (modelView != null) {
Map<String, Object> model = modelView.getModel();
// Sanitize sensitive data before rendering
if (model.containsKey("password")) {
model.remove("password");
}
// Add common data to all views
model.put("appVersion", "2.1.0");
model.put("currentTime", LocalDateTime.now());
}
}
The afterCompletion Method
This method executes after the entire request processing chain completes, encluding view rendering. It's guaranteed to execute even if an exception occurred. Use it for:
- Resource cleanup (closing database connections, file handles)
- Performence logging
- Audit trail recording
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
if (duration > 1000) {
log.warn("Slow request detected: {} took {}ms", request.getRequestURI(), duration);
}
if (ex != null) {
log.error("Request failed with exception", ex);
}
}
Multiple Interceptor Execution Flow
When multiple interceptors are configured, they form an interceptor chain with specific execution patterns:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/orders/**"/>
<bean class="com.example.mvc.interceptor.LoggingInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/orders/**"/>
<bean class="com.example.mvc.interceptor.ValidationInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
Execution sequence for a request to /orders/create:
LoggingInterceptor.preHandle → First
ValidationInterceptor.preHandle → Second
↓
[Controller executes here]
↓
ValidationInterceptor.postHandle → Second (reverse order)
LoggingInterceptor.postHandle → First
↓
[JSP/View renders]
↓
ValidationInterceptor.afterCompletion → Second (reverse order)
LoggingInterceptor.afterCompletion → First
The execution follows a FILO (First In, Last Out) pattern:
preHandlemethods execute in configuration orderpostHandlemethods execute in reverse configuration orderafterCompletionmethods execute in reverse configuration order
If any preHandle returns false, only that interceptor's afterCompletion is called for previously passed interceptors.
Practical Example: Authentication and Logging
public class AuditInterceptor implements HandlerInterceptor {
private static final Logger auditLog = LoggerFactory.getLogger(AuditInterceptor.class);
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
request.setAttribute("requestStartTime", System.currentTimeMillis());
String method = request.getMethod();
String uri = request.getRequestURI();
String userId = request.getRemoteUser();
auditLog.info("Request initiated: {} {} by {}", method, uri, userId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelView) throws Exception {
String viewName = modelView != null ? modelView.getViewName() : "no view";
auditLog.debug("View resolved to: {}", viewName);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute("requestStartTime");
long duration = System.currentTimeMillis() - startTime;
auditLog.info("Request completed in {}ms with status: {}",
duration, response.getStatus());
}
}
This interceptor leverages Spring's dependency injection capability—something not possible with standard Servlet filters—demonstrating a key advantage of using interceptors for cross-cutting concerns within a Spring application.