The implementation of interface org.springframework.web.servlet.HandlerInterceptor is used to intercept requests to the controllers.
Definition of HandlerInterceptorVersion: 7.0.6 package org.springframework.web.servlet;
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception;
default void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable ModelAndView modelAndView)
throws Exception;
default void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable Exception ex)
throws Exception;
}
- preHandle: is called before the target handler method is invoked for a given request. If this method returns false, further processing is abandoned i.e. the handler method is not called.
- postHandle: is called after execution of target handler, but before the view is rendered. Good for post processing of what we started in preHandler method e.g. performance logging.
- afterCompletion: is called after rendering the view. Good for resource cleanups
HandlerInterceptor vs Servlet Filter
HandlerInterceptor is basically similar to a Servlet Filter, but in contrast to the latter it just allows custom pre-processing with the option of prohibiting the execution of the handler itself, and custom post-processing. Filters are more powerful, for example they allow for exchanging the request and response objects that are handed down the chain. Note that a filter gets configured in web.xml, a HandlerInterceptor in the application context.
As a basic guideline, fine-grained handler-related preprocessing tasks are candidates for HandlerInterceptor implementations, especially factored-out common handler code and authorization checks. On the other hand, a Filter is well-suited for request content and view content handling, like multipart forms and GZIP compression. This typically shows when one needs to map the filter to certain content types (for example, images), or to all requests.
Interceptors are best kept for cross-cutting technical concerns such as request logging, performance monitoring, setting common model attributes, or measuring execution time. They are not a security layer.
Example
The Logging HandlerInterceptor
In this example we are going to use HandlerInterceptorAdapter which implements HandlerInterceptor interface. HandlerInterceptorAdapter is based on adapter design pattern.
package com.logicbig.example;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
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 {
request.setAttribute("startTime", System.currentTimeMillis());
HandlerMethod hm = (HandlerMethod) handler;
System.out.printf("From Interceptor, target handler: %s#%s%n",
hm.getBeanType().getSimpleName(),
hm.getMethod().getName());
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
Long startTime = (Long) request.getAttribute(
"startTime");
modelAndView.addObject(
"footerNote",
String.format("Rendered in %s ms",
System.currentTimeMillis() - startTime));
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
long elapsed = System.currentTimeMillis() -
(Long) request.getAttribute("startTime");
System.out.printf("From Interceptor: %s completed in %sms%n",
request.getRequestURI(),
elapsed);
}
}
In the above example, we are implementing HandlerInterceptor directly. Before Spring 5.0, the HandlerInterceptor interface did not yet use Java 8 default methods, so we needed to extend HandlerInterceptorAdapter, which implemented HandlerInterceptor using the adapter design pattern. HandlerInterceptorAdapter was deprecated in Spring 5.3 and removed completely in Spring 6.
Registering the Interceptor
package com.logicbig.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@EnableWebMvc
@Configuration
@ComponentScan
public class MyWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor());
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
}
The Controller
package com.logicbig.example;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class AppController {
@GetMapping(value = "app/**")
public String handleAppRequest(Model model,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
System.out.println("From handler: " + requestURI);
model.addAttribute("uri", requestURI);
return "app-page";
}
}
JSP page
src/main/webapp/WEB-INF/views/app-page.jsp<%@ page language="java"
contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<html>
<body>
<h3> App Page </h3>
<p>${uri}</p>
<br/>
<footer>
${footerNote}
</footer>
</body>
</html>
Running the example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
Enter url in the browser http://localhost:8080/app/test
Using curl
$ curl -s "http://localhost:8080/app/test2"
<html> <body> <h3> App Page </h3> <p>/app/test2</p> <br/> <footer> Rendered in 4 ms </footer> </body> </html>
Integration Test
package com.logicbig.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.test.web.servlet.assertj.MvcTestResult;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@SpringJUnitWebConfig(MyWebConfig.class)
public class AppControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvcTester mockMvcTester;
@BeforeEach
public void setup() {
this.mockMvcTester = MockMvcTester.from(this.wac);
}
@Test
public void testLogin() {
long time = System.currentTimeMillis();
MvcTestResult result = this.mockMvcTester.get()
.uri("/app/test")
.exchange();
assertThat(result).hasStatusOk();
assertThat((Long) result.getRequest()
.getAttribute("startTime"))
.isGreaterThan(time);
assertThat(result).model()
.containsKeys("uri", "footerNote");
}
}
mvn clean test -Dtest="AppControllerTest" Output$ mvn clean test -Dtest="AppControllerTest" [INFO] Scanning for projects... [INFO] [INFO] ----------< com.logicbig.example:spring-handler-interceptor >----------- [INFO] Building spring-handler-interceptor 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ spring-handler-interceptor --- [INFO] Deleting D:\example-projects\spring-mvc\spring-handler-interceptor\target [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-handler-interceptor --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-handler-interceptor\src\main\resources [INFO] [INFO] --- compiler:3.15.0:compile (default-compile) @ spring-handler-interceptor --- [INFO] Recompiling the module because of changed source code. [INFO] Compiling 4 source files with javac [debug target 25] to target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-handler-interceptor --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-handler-interceptor\src\test\resources [INFO] [INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ spring-handler-interceptor --- [INFO] Recompiling the module because of changed dependency. [INFO] Compiling 2 source files with javac [debug target 25] to target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ spring-handler-interceptor --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.AppControllerTest From Interceptor, target handler: AppController#handleAppRequest From handler: /app/test From Interceptor: /app/test completed in 35ms [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.331 s -- in com.logicbig.example.AppControllerTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 9.204 s [INFO] Finished at: 2026-08-15T19:26:43+08:00 [INFO] ------------------------------------------------------------------------ INFO: Completed initialization in 2 ms
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 3.2.9.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- spring-test 7.0.6 (Spring TestContext Framework)
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
- hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
- assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
- JDK 25
- Maven 3.9.11
|
|