Skip to main content

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:

  1. Before the handler execution (preHandle method)
  2. After the handler execution (postHandle method)
  3. 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:

java
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:

java
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.

java
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
  • Parameters:

    • request: The HTTP request
    • response: The HTTP response
    • handler: 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.

java
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)
  • 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.

java
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)
  • 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:

java
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:

java
@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:

java
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:

java
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:

java
@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:

  1. LoggingInterceptor.preHandle
  2. AuthenticationInterceptor.preHandle
  3. PerformanceInterceptor.preHandle
  4. Controller execution
  5. PerformanceInterceptor.postHandle
  6. AuthenticationInterceptor.postHandle
  7. LoggingInterceptor.postHandle
  8. View rendering
  9. PerformanceInterceptor.afterCompletion
  10. AuthenticationInterceptor.afterCompletion
  11. LoggingInterceptor.afterCompletion

Advanced Techniques

Accessing Handler Information

The handler parameter gives you information about the controller method being called:

java
@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:

java
// Custom annotation
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresAuth {
String role() default "USER";
}
java
// 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

Exercises

  1. Basic Logging Interceptor
    Create an interceptor that logs the following information for each request:

    • Request URI
    • HTTP method
    • Client IP address
    • Request processing time
  2. Rate Limiting Interceptor
    Implement an interceptor that limits the number of requests from a single IP address to 100 requests per minute.

  3. 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.

  4. 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! :)