Spring
HandlerInterceptorintercepts HTTP requests at the controller level, i.e., after Spring has matched the request to a specific handler (controller method). Interceptors are mainly used for cross-cutting concerns like logging, authentication, metrics, etc., without touching the servlet layer directly
false), log, authenticate, or modify the requestModelAndView or add attributes@Slf4j
@Component
public class OpenApiInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("pre handle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("post Handle");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("after completion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
@Component does not automatically attach it to Spring MVC's request handling.HandlerInterceptor beans, but they do nothing until we register them via a configuration.@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private OpenApiInterceptor openApiInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(openApiInterceptor)
.addPathPatterns("/**");
}
}
@Configuration marks this class as a Spring configuration class. Spring will detect it and use it to configure MVC settings.addInterceptors() is called by Spring MVC to register any interceptors.registry.addInterceptor(openApiInterceptor) addes my OpenApiInterceptor to the interceptor chain..addPathPatterns("/**) tells Spring to apply this interceptor to all paths, but we can also limit it so specific paths, e.g., /api/**.log() from the OpenApiInterceptor.Incoming HTTP Request
|
v
+----------------------+
| Servlet Filter(s) | <- e.g., LoggerFilter
| (implements Filter) |
+----------------------+
|
v
+---------------------------+
| DispatcherServlet |
| (Spring MVC front controller) |
+---------------------------+
|
v
+---------------------------+
| HandlerInterceptor(s) | <- e.g., OpenApiInterceptor
| preHandle() |
+---------------------------+
|
v
+---------------------------+
| Controller | <- e.g., UserApiController
+---------------------------+
|
v
+---------------------------+
| HandlerInterceptor(s) |
| postHandle() |
+---------------------------+
|
v
+---------------------------+
| View Rendering / Response | (if any)
| afterCompletion() | <- HandlerInterceptor
+---------------------------+
|
v
+----------------------+
| Servlet Filter(s) | <- LoggerFilter can do post-processing here too
+----------------------+
|
v
HTTP Response sent to client

2025-08-19T00:17:38.895+08:00 INFO 22757 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : before entry //filter
2025-08-19T00:17:38.912+08:00 INFO 22757 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : pre handle //interceptor
2025-08-19T00:17:38.999+08:00 INFO 22757 --- [filter] [nio-8080-exec-1] c.e.filter.controller.UserApiController : UserRequest(name=Choi, phoneNumber=null, email=choi@gmail.com, age=45) //Controller
2025-08-19T00:17:39.028+08:00 INFO 22757 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : post Handle //Interceptor
2025-08-19T00:17:39.029+08:00 INFO 22757 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : after completion //Interceptor
2025-08-19T00:17:39.030+08:00 INFO 22757 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : req : {
"name": "Choi",
"phoneNumber": "010-2321-2321",
"email": "choi@gmail.com",
"age": 45
} //Filter
2025-08-19T00:17:39.030+08:00 INFO 22757 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : res : {"name":"Choi","phone_number":null,"email":"choi@gmail.com","age":45}
2025-08-19T00:17:39.030+08:00 INFO 22757 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : after entry
@Target(value = { ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApi {
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})ElementType.TYPE can be placed on classes, interfaces, enumsElementType.METHOD can be placed on methods@Retention(RetentionPolicy.RUNTIME)RUNTIME -> the annotation will be available runtime, and reflection (e.g. Spring interceptors, AOP, or custom code) can read it.preHandle() method in our interceptor class as below:@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("pre handle");
var handlerMethod = (HandlerMethod) handler;
// Check if the method itself has @OpenApi
var methodLevel = handlerMethod.getMethodAnnotation(OpenApi.class);
if(methodLevel != null){
log.info("method level");
return true; // allow request to proceed
}
// Check if the class has @OpenApi
var classLevel = handlerMethod.getBeanType().getAnnotation(OpenApi.class);
if(classLevel != null){
log.info("class level");
return true; // allow request to proceed
}
log.info("not @OpenApi : {}", request.getRequestURI());
return false; // block request (controller method will not run)
}
var handlerMethod = (HandlerMethod) handler;, what is a handler here?HandlerInterceptor, the preHandle method signature is:boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) handler object represents the thing that will handle the reqeust.handler will be of type HandlerMethod.HandlerMethod?HandlerMethod API, which gives us reflective access. HandlerMethod provides methods like:getMethod() // the actual java.lang.reflect.Method
getBeanType() // the controller's class
getMethodAnnotation(...) // check if a method has a certain annotation
getBean() // the actual controller objecthandler (since it's just an Object by default).UserApiController:public class UserApiController {
@OpenApi
@PostMapping("")
public UserRequest register(
@RequestBody
UserRequest userRequest
){
log.info("{}", userRequest);
return userRequest;
}
@GetMapping("/hello")
public void hello(){
log.info("Hello");
}
}
/hello endpoint, we get the following log:2025-08-19T00:46:39.377+08:00 INFO 24943 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : before entry
2025-08-19T00:46:39.396+08:00 INFO 24943 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : pre handle
2025-08-19T00:46:39.397+08:00 INFO 24943 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : not @OpenApi : /api/user/hello
2025-08-19T00:46:39.399+08:00 INFO 24943 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : req :
2025-08-19T00:46:39.399+08:00 INFO 24943 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : res :
2025-08-19T00:46:39.399+08:00 INFO 24943 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : after entry
hello() method mapped to /hello endpoint was not annotated with @OpenApi and the UserApiController class was not annotated with @OpenApi, the preHandle() method returned false and the request was not proceeded. UserApiController class with @OpenApi annotation, all methods (including hello()) method will have the annotation, therefore the request will be proceeded, as shown below:@OpenApi
@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserApiController {
@OpenApi
@PostMapping("")
public UserRequest register(
@RequestBody
UserRequest userRequest
){
log.info("{}", userRequest);
return userRequest;
}
@GetMapping("/hello")
public void hello(){
log.info("Hello");
}
}
2025-08-19T00:52:01.599+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : before entry
2025-08-19T00:52:01.620+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : pre handle
2025-08-19T00:52:01.621+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : class level
2025-08-19T00:52:01.637+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] c.e.filter.controller.UserApiController : Hello
2025-08-19T00:52:01.656+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : post Handle
2025-08-19T00:52:01.656+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] c.e.f.interceptor.OpenApiInterceptor : after completion
2025-08-19T00:52:01.657+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : req :
2025-08-19T00:52:01.659+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : res :
2025-08-19T00:52:01.659+08:00 INFO 25315 --- [filter] [nio-8080-exec-1] com.example.filter.filter.LoggerFilter : after entry
WebConfig configuration:@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private OpenApiInterceptor openApiInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(openApiInterceptor)
.addPathPatterns("/**");
//we can add more interceptor(s)
registry.addInterceptor(someOtherInterceptor)
.addPathPatterns("/**");
}
}
.order().