Hello! Let's dive into the next lesson.
In our previous lessons, we built a flexible system for identifying clients using our RateLimitKeyResolver interface. You've successfully implemented resolvers for API keys and authenticated User IDs, which are fundamental for any multi-tenant service.
Today, we'll add another essential strategy to our collection. We'll focus on identifying requests not by who is making them, but by what they are asking for.
Your learning outcome for this lesson:
You will implement a key resolver to identify a request by its endpoint path. This capability is the cornerstone of applying different rate limits to different parts of your API, allowing you to protect expensive operations more strictly than cheaper ones.
1. Why Rate Limit by Endpoint?
Before we jump into the code, let's establish why this is so important in a real-world system. Not all API endpoints are created equal:
- A
GET /api/productsendpoint might be a lightweight database read, capable of handling hundreds of requests per minute. - A
POST /api/ordersendpoint could trigger a complex workflow involving payment processing, inventory updates, and sending notifications. This is an expensive operation that you'd want to limit more strictly. - A
POST /api/users/reset-passwordendpoint is a security-sensitive operation that should be heavily restricted to prevent abuse.
By generating a rate-limiting key based on the endpoint path (e.g., /api/products), we can later create a configuration that maps these specific paths to different rules. This gives us fine-grained control over our API's usage and protection. This is a standard pattern in API gateways and production infrastructure.
2. Accessing the Request Path in an Interceptor
Our RateLimitKeyResolver interface provides the resolve(HttpServletRequest request) method. This HttpServletRequest object is our gateway to all information about the incoming request, including its path.
The key method we need is request.getRequestURI(). This method returns the part of the URL from the protocol name up to the query string.
For example, for a request to http://localhost:8080/api/users/123?verbose=true, request.getRequestURI() would return the string "/api/users/123".
The video below provides a practical demonstration of working with the HttpServletRequest object inside an interceptor, similar to our RateLimitingInterceptor.
Spring Interceptors Explained: Retrieve Query Params and More!
The video 'Spring Interceptors Explained' by CodeSnippet provides an excellent walkthrough of how interceptors work. Watching this section will help you visualize how we can access request details within the preHandle method, which is where our key resolver is invoked.
Please watch the segment from 00:17:09 to 00:22:06. The presenter uses the debugger to inspect the request object. Pay close attention to how they access request information. While they demonstrate request.getQueryString(), the principle for accessing request.getRequestURI() is exactly the same. This reinforces that all the necessary data is readily available to us within the interceptor's execution context.
This confirms that getting the path is straightforward. Now, let's implement the resolver.
3. Implementing the EndpointPathResolver
Create a new Java class named EndpointPathResolver. This implementation will be concise, but its role in the overall architecture is significant.
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
/**
* Resolves the rate-limiting key from the request's URI.
* This allows for applying rate limits on a per-endpoint basis.
*/
@Component("endpointPathResolver")
public class EndpointPathResolver implements RateLimitKeyResolver {
@Override
public String resolve(HttpServletRequest request) {
// Return the URI of the request, which uniquely identifies the endpoint path.
// For example, for a request to "http://example.com/api/users?id=1",
// this will return "/api/users".
return request.getRequestURI();
}
}
Key Points:
@Component("endpointPathResolver"): We register this as a Spring bean with a unique name, allowing us to easily inject it into ourRateLimitingInterceptoror other components.request.getRequestURI(): This single method call is all we need. It reliably extracts the path, which serves as a perfect key for endpoint-specific rules.
To visualize what part of the URL getRequestURI() captures, consider the following diagram. It focuses on the path segment of the URL.

4. Industry Validation and Nuances
Our approach is a common industry practice. The article below discusses implementing rate limiting and explicitly shows how the request path is used to form a unique key.
Rate Limit Per Endpoint in Spring Boot APIs
The article 'Rate Limit Per Endpoint in Spring Boot APIs' provides a great overview of the mechanics. It validates our approach of using the request URI as part of the rate-limiting key.
Please read the sections 'Identifying Clients' and 'Filters in Spring Boot'. Notice how the example buildRateKey method combines a client identifier with the path obtained from request.getRequestURI(). The RateLimitFilter example also shows the path being extracted for the rate-limiting service. This confirms that using the URI is a standard and effective strategy.
A Quick Note on Alternatives
You might see other implementations that use reflection to get the controller's method name (e.g., method.getName()) as the key. The article How I Built a Simple Rate Limiter... (resource_id: LINK) from our resource pool uses this technique.
While that works, using request.getRequestURI() is often more robust for two reasons:
- Specificity: Multiple URL paths can be mapped to the same controller method (e.g.,
/api/ordersand/api/legacy-orders). Using the URI allows you to rate-limit them differently, whereasmethod.getName()would treat them as the same. - No Reflection: It avoids the slight performance overhead and complexity of using Java reflection to inspect the handler method.
Our current implementation is simple, direct, and highly effective.
Key Takeaways
- You've implemented an
EndpointPathResolverusingrequest.getRequestURI()to identify requests by their path. - You understand the importance of this strategy for applying fine-grained rate limits to different API endpoints based on their cost or sensitivity.
- This new resolver fits perfectly into our
RateLimitKeyResolverstrategy pattern, further enhancing the flexibility of our system. - You've seen that this approach is validated by common industry practices for building rate limiters and API gateways.
Next Steps
Excellent work! We now have resolvers that can identify a client by API key, user ID, or the endpoint they are trying to access. Each of these strategies is powerful on its own. However, the true power of a multi-tenant system comes from combining them.
In our next lesson, we will create a composite key resolver that combines multiple identifiers. This will allow us to enforce rules like "limit user 'test-user' to 10 requests per minute on the /api/orders endpoint," unlocking a new level of control for our rate limiter.