Hello! Welcome back to our course on building a production-grade rate limiter.
In the last lesson, you mastered externalized configuration by refactoring our API to pull settings from application.yml using the robust @ConfigurationProperties pattern. This separation of code from configuration is a vital skill for creating maintainable, production-ready applications.
So far, all requests to our /greeting endpoint go directly to the GreetingController. To implement rate limiting, we need a mechanism to inspect and potentially block these requests before they ever reach the controller logic. This type of functionality, which cuts across multiple parts of an application (like logging, security, or in our case, rate limiting), is known as a cross-cutting concern.
This lesson introduces the perfect tool for this job in Spring: the HandlerInterceptor.
Your learning outcome for this lesson:
Implement request interceptors using HandlerInterceptor for cross-cutting concerns.
1. Understanding the Interception Point
Before we write any code, let's understand where an interceptor fits into the Spring MVC request lifecycle. When a client sends an HTTP request to your application, it doesn't go straight to your @RestController. It first goes through a central component called the DispatcherServlet, which then routes it to the appropriate controller method.
A HandlerInterceptor allows us to hook into this process at critical moments.

For our rate limiter, the most important point of interception is before the controller is executed. This allows us to decide if the request should proceed or be rejected immediately, saving valuable server resources.
To get a solid conceptual foundation, please read the first section of this excellent article from Baeldung.
Introduction to Spring MVC HandlerInterceptor
The article 'Introduction to Spring MVC HandlerInterceptor' on Baeldung provides a clear explanation of what interceptors are and the problem they solve.
Read the 'Overview' and 'Spring MVC Handler' sections to understand the role of interceptors in handling cross-cutting concerns.
2. The HandlerInterceptor Interface
To create an interceptor, you implement the HandlerInterceptor interface. This interface defines three primary methods, each corresponding to a different stage in the request lifecycle.
Spring Interceptors Explained: Retrieve Query Params and More!
The video 'Spring Interceptors Explained' by CodeSnippet provides a great visual and practical walkthrough of the HandlerInterceptor interface and its methods.
Watch the section from 07:34 to 12:14, which introduces the HandlerInterceptor interface and explains the purpose of preHandle, postHandle, and afterCompletion.
Let's summarize the key methods and their relevance to our goal:
-
boolean preHandle(...):- When: Executed before the controller handler method.
- Purpose: This is our main workhorse. We will use it to check if a request is within its allowed rate limit.
- Return Value: Critically, it returns a boolean.
true: The request continues to the next interceptor or the controller itself.false: The request processing stops here. The controller method is never called. This is exactly what we need to reject an over-the-limit request.
-
void postHandle(...):- When: Executed after the controller method returns, but before the response is sent to the client.
- Purpose: Useful for modifying the model or response headers before the view is rendered. We won't use this for our core rate-limiting logic, but it's good to know.
-
void afterCompletion(...):- When: Executed after the complete request has finished and the response has been sent.
- Purpose: Ideal for cleanup tasks, such as logging request duration or releasing resources. It will always be called, even if an exception occurred.
3. Implementing and Registering Your First Interceptor
Now, let's get hands-on. We'll create a simple logging interceptor to see the execution flow in action. This will serve as the blueprint for our rate-limiting interceptor later.
Exercise: Create a Logging Interceptor
Follow along with the video to implement and register an interceptor that logs messages at each stage of the request lifecycle. This will involve two steps:
- Creating the interceptor class.
- Creating a configuration class to register it.
Spring Interceptors Explained: Retrieve Query Params and More!
This portion of the 'Spring Interceptors Explained' video will guide you through the entire process of creating, registering, and testing a basic interceptor.
Follow the coding demonstration from 08:27 to 21:17. Create the Interceptor (08:27 - 14:05): Create a LoggingInterceptor class that implements HandlerInterceptor and add print statements to each method. Register the Interceptor (14:05 - 17:09): Create a WebConfig class that implements WebMvcConfigurer and use the addInterceptors method to register your LoggingInterceptor. Test It (17:09 - 21:17): Run your application and use a tool like Postman or your web browser to hit the /greeting endpoint. Observe the console output to see the execution order.
After completing this exercise, your console output for a single request should look something like this, confirming the execution order:
preHandle request URI is /greeting
Request is reached to controller
postHandle executed for request URI /greeting
afterCompletion request completed
4. Advanced Configuration for Real-World Use
Instantiating the interceptor with new LoggingInterceptor() inside the configuration works, but it's not ideal. For our rate limiter, the interceptor will need access to other services (like configuration properties or a Redis connection). To enable this, we should let Spring manage the interceptor as a bean.
Furthermore, we rarely want an interceptor to apply to every single endpoint. For example, you wouldn't rate-limit a /health check endpoint. We need to selectively apply our interceptor.
This is where the true power of the InterceptorRegistry comes in.
How Spring Boot Configures and Executes Interceptors
The article 'How Spring Boot Configures and Executes Interceptors' provides clear examples of how to turn an interceptor into a managed bean and apply it to specific URL patterns.
Read these two sections: 'Interceptor Class as a Bean': This shows how to annotate your interceptor with @Component and inject it into your WebMvcConfigurer. 'Path Matching Basics': This is crucial. Focus on how addPathPatterns() and excludePathPatterns() are used to control exactly which endpoints the interceptor applies to.
Exercise 2: Refine Your Interceptor Configuration
Let's apply these advanced concepts to your project.
-
Make your interceptor a Spring Bean:
- Add the
@Componentannotation to yourLoggingInterceptorclass.
// In LoggingInterceptor.java import org.springframework.stereotype.Component; @Component // <-- Add this public class LoggingInterceptor implements HandlerInterceptor { ... } - Add the
-
Inject the bean into your
WebConfig:- Modify your
WebConfigto inject theLoggingInterceptorbean via its constructor instead of creating a new instance.
// In WebConfig.java @Configuration public class WebConfig implements WebMvcConfigurer { private final LoggingInterceptor loggingInterceptor; // Inject the bean via constructor public WebConfig(LoggingInterceptor loggingInterceptor) { this.loggingInterceptor = loggingInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { // Use the injected bean registry.addInterceptor(loggingInterceptor); } } - Modify your
-
Apply Path Matching:
- Let's configure the interceptor to only apply to our
/greetingendpoint. Modify theaddInterceptorsmethod.
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loggingInterceptor) .addPathPatterns("/greeting"); // <-- Add this line } - Let's configure the interceptor to only apply to our
-
Test your changes:
- Re-run your application.
- A request to
http://localhost:8080/greetingshould still produce the log messages. - If you had another endpoint (e.g., one provided by Spring Boot Actuator like
http://localhost:8080/actuator/health), you would notice that the interceptor logs do not appear for it. You have successfully limited the scope of your interceptor.
This pattern of creating an interceptor as a managed @Component and then registering it with specific addPathPatterns is precisely the architecture we will use to build our flexible, rule-based rate limiter.
Key Takeaways
- Cross-Cutting Concerns: Interceptors are Spring's primary mechanism for handling logic that applies across multiple endpoints, such as rate limiting, logging, or authentication.
HandlerInterceptor: This is the core interface with three methods:preHandle,postHandle, andafterCompletion, which hook into different stages of the request lifecycle.preHandleis Key: For validation and access control like rate limiting,preHandleis the most important method. Returningfalsefrom it efficiently stops the request from being processed further.- Registration is Mandatory: An interceptor must be registered using a class that implements
WebMvcConfigurerand overrides theaddInterceptorsmethod. - Scope Control: Use
addPathPatterns()andexcludePathPatterns()to precisely control which API endpoints your interceptor applies to.
Next Steps
With this lesson, you have completed the foundational module on Spring Boot! You now have a working, configurable API service and, most importantly, the architectural hook—the HandlerInterceptor—where our rate-limiting logic will reside.
In the next module, we will shift our focus from the "how" of Spring Boot to the "what" of rate limiting. The next lesson begins our journey into Rate Limiting Principles and Architectural Design, where we will start by exploring the fundamental need for rate limiting and then dive into the details of the first algorithm: the Fixed Window Counter.