Spring MVC Interceptors
Introduction
In the world of Spring MVC, interceptors provide a powerful mechanism to intercept HTTP requests before they reach your controllers or after the controller has processed them. Interceptors are similar to filters in the Servlet API but are specifically designed to work with the Spring MVC framework. They allow you to perform cross-cutting concerns such as logging, authentication, or modifying the request or response objects before or after the actual handler execution.
In this tutorial, you'll learn:
- What Spring MVC Interceptors are and why they're useful
- How to create and configure interceptors
- The interceptor lifecycle methods
- Practical examples of common interceptor use cases
Understanding Spring MVC Interceptors
What Are Interceptors?
Interceptors in Spring MVC are components that can "intercept" the request processing pipeline at three different points:
- Before the handler execution (
preHandle
method) - After the handler execution (
postHandle
method) - After the complete request has been finished (
afterCompletion
method)
Here's a visual representation of the request flow with interceptors:
Client Request → Interceptor.preHandle → Controller → Interceptor.postHandle → View Rendering → Interceptor.afterCompletion → Client Response
Why Use Interceptors?
Interceptors are particularly useful for:
- Logging: Track request/response details
- Authentication and Authorization: Verify user credentials before allowing access
- Performance Monitoring: Measure execution time of requests
- Data Manipulation: Modify request or response data globally
- Session Management: Handle session-related operations
Creating Your First Interceptor
To create an interceptor, you need to implement the HandlerInterceptor
interface or extend the HandlerInterceptorAdapter
class (deprecated in Spring 5.3).
Basic Interceptor Implementation
Here's a simple logging interceptor:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("PreHandle: " + request.getMethod() + " " + request.getRequestURI());
// Returning true allows the request to continue to the controller
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("PostHandle: " + request.getMethod() + " " + request.getRequestURI() +
" with status: " + response.getStatus());
// This runs after the controller but before the view is rendered
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("AfterCompletion: " + request.getMethod() + " " + request.getRequestURI() +
" completed with status: " + response.getStatus());
// This runs after the view has been rendered
if (ex != null) {
System.out.println("Exception occurred: " + ex.getMessage());
}
}
}
Registering Your Interceptor
To register your interceptor, you need to add it to the interceptor registry in a configuration class:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor());
// You can also specify paths to include/exclude
// registry.addInterceptor(new LoggingInterceptor())
// .addPathPatterns("/api/**")
// .excludePathPatterns("/api/admin/**");
}
}
Understanding the Interceptor Methods
Let's look at each interceptor method in detail:
1. preHandle Method
The preHandle
method is called before the actual handler (controller) is executed.
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
-
Parameters:
request
: The HTTP requestresponse
: The HTTP responsehandler
: The selected handler for the request (usually a controller method)
-
Return Value:
true
: The request processing continues (calls the controller)false
: The request processing stops (doesn't call the controller)
-
Use Cases:
- Authentication/authorization checks
- Logging request information
- Setting request attributes before controller execution
2. postHandle Method
The postHandle
method is called after the handler execution but before the view is rendered.
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
-
Parameters:
- Same as
preHandle
plus: modelAndView
: The ModelAndView object that the handler returned (can be null)
- Same as
-
Use Cases:
- Modify the ModelAndView
- Add common attributes to the model
- Logging controller execution results
3. afterCompletion Method
The afterCompletion
method is called after the complete request has been finished and the view was rendered.
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
-
Parameters:
- Same as
preHandle
plus: ex
: Exception raised during handler execution (null if no exception)
- Same as
-
Use Cases:
- Resource cleanup
- Logging request completion
- Exception handling and logging
Practical Examples
Example 1: Authentication Interceptor
Here's an example of an interceptor that checks if a user is logged in before accessing protected resources:
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Check if user is logged in
if (request.getSession().getAttribute("user") == null) {
// User is not logged in, redirect to login page
response.sendRedirect("/login");
return false; // Stop further processing
}
return true; // User is logged in, continue
}
}
Register it for specific paths:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthenticationInterceptor())
.addPathPatterns("/secure/**", "/profile/**")
.excludePathPatterns("/public/**", "/login", "/register");
}
}
Example 2: Performance Monitoring Interceptor
This interceptor measures the time taken to process a request:
public class PerformanceInterceptor implements HandlerInterceptor {
private ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
startTimeThreadLocal.set(startTime);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long startTime = startTimeThreadLocal.get();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
System.out.println("Request URL: " + request.getRequestURI() +
" - Execution Time: " + executionTime + "ms");
// Clean up the ThreadLocal to prevent memory leaks
startTimeThreadLocal.remove();
}
}
Example 3: Localization Interceptor
This interceptor sets the locale based on a request parameter:
public class LocaleChangeInterceptor implements HandlerInterceptor {
private String paramName = "lang";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String newLocale = request.getParameter(paramName);
if (newLocale != null) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver != null) {
localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale));
}
}
return true;
}
public void setParamName(String paramName) {
this.paramName = paramName;
}
}
Working with Multiple Interceptors
You can register multiple interceptors, and they will execute in the order they are registered:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// First interceptor
registry.addInterceptor(new LoggingInterceptor());
// Second interceptor
registry.addInterceptor(new AuthenticationInterceptor())
.addPathPatterns("/secure/**");
// Third interceptor
registry.addInterceptor(new PerformanceInterceptor());
}
}
The execution order will be:
- LoggingInterceptor.preHandle
- AuthenticationInterceptor.preHandle
- PerformanceInterceptor.preHandle
- Controller execution
- PerformanceInterceptor.postHandle
- AuthenticationInterceptor.postHandle
- LoggingInterceptor.postHandle
- View rendering
- PerformanceInterceptor.afterCompletion
- AuthenticationInterceptor.afterCompletion
- LoggingInterceptor.afterCompletion
Advanced Techniques
Accessing Handler Information
The handler
parameter gives you information about the controller method being called:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
String controllerName = handlerMethod.getBeanType().getSimpleName();
String methodName = handlerMethod.getMethod().getName();
System.out.println("Calling controller: " + controllerName + ", method: " + methodName);
}
return true;
}
Using Annotations with Interceptors
You can create custom annotations and check for them in your interceptors:
// Custom annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresAuth {
String role() default "USER";
}
// Interceptor that checks for the annotation
public class RoleSecurityInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// Look for our annotation on the method
RequiresAuth requiresAuth = handlerMethod.getMethodAnnotation(RequiresAuth.class);
// If not on method, look at class level
if (requiresAuth == null) {
requiresAuth = handlerMethod.getBeanType().getAnnotation(RequiresAuth.class);
}
if (requiresAuth != null) {
// Get required role from annotation
String requiredRole = requiresAuth.role();
// Get user's roles (from session, token, etc.)
String userRole = (String) request.getSession().getAttribute("userRole");
if (userRole == null || !userRole.equals(requiredRole)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Insufficient permissions");
return false;
}
}
}
return true;
}
}
Summary
Spring MVC interceptors provide a powerful way to handle cross-cutting concerns in your web applications. They allow you to:
- Intercept requests before they reach your controller
- Modify request/response objects
- Process requests after controller execution but before view rendering
- Perform cleanup operations after request completion
By implementing the HandlerInterceptor
interface, you can create custom interceptors that handle authentication, logging, performance monitoring, and many other concerns in a clean, centralized way.
The key advantages of using interceptors include:
- Separation of cross-cutting concerns from business logic
- Reusable components that can be applied to multiple controllers
- Fine-grained control over which requests are intercepted through path patterns
Additional Resources and Exercises
Resources
- Official Spring Documentation on Interceptors
- Spring MVC Handler Interceptors Example
- Spring Boot Interceptor Example
Exercises
-
Basic Logging Interceptor
Create an interceptor that logs the following information for each request:- Request URI
- HTTP method
- Client IP address
- Request processing time
-
Rate Limiting Interceptor
Implement an interceptor that limits the number of requests from a single IP address to 100 requests per minute. -
Maintenance Mode Interceptor
Create an interceptor that returns a maintenance page for all requests except those coming from admin IPs when a "maintenance mode" flag is enabled. -
Request Validation Interceptor
Build an interceptor that validates incoming requests for required parameters and returns an error if they're missing.
By mastering interceptors, you'll have a powerful tool in your Spring MVC arsenal to handle many common web application requirements in a clean and efficient way.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)