Implementing API Rate Limiting
1. Using Bucket4j for Rate Limiting
Example: Bucket4j dependencies
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j_jdk17-core</artifactId>
<version>8.10.1</version>
</dependency>
2. Configuring Rate Limits
Example: Token bucket with 100 req/min bandwidth
Bucket bucket = Bucket.builder()
.addLimit(Bandwidth.builder().capacity(100).refillIntervally(100, Duration.ofMinutes(1)).build())
.build();
3. Implementing Filter-Based Rate Limiting
Example: Per-IP rate limit filter
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
@Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
Bucket b = buckets.computeIfAbsent(key(req), k -> newBucket());
ConsumptionProbe probe = b.tryConsumeAndReturnRemaining(1);
if (probe.isConsumed()) {
res.addHeader("X-RateLimit-Remaining", String.valueOf(probe.getRemainingTokens()));
chain.doFilter(req, res);
} else {
res.setStatus(429);
res.addHeader("Retry-After", String.valueOf(probe.getNanosToWaitForRefill() / 1_000_000_000));
}
}
private String key(HttpServletRequest r) { return r.getRemoteAddr(); }
private Bucket newBucket() { /* see 49.2 */ return null; }
}
4. Using Redis for Distributed Rate Limiting
Example: Distributed rate limit dependencies
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j_jdk17-redis-common</artifactId>
</dependency>
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j_jdk17-lettuce</artifactId>
</dependency>
Note: A distributed proxy ensures all app instances share the same token bucket
— required behind load balancers.
5. Implementing User-Based Rate Limits
Example: Rate limit key: authenticated user or IP
private String key(HttpServletRequest r) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return (auth != null && auth.isAuthenticated()) ? "user:" + auth.getName() : "ip:" + r.getRemoteAddr();
}
6. Implementing Endpoint-Specific Rate Limits
Example: Per-endpoint bandwidth limits
private static final Map<String, Bandwidth> LIMITS = Map.of(
"/api/auth/login", Bandwidth.simple(5, Duration.ofMinutes(1)),
"/api/search", Bandwidth.simple(60, Duration.ofMinutes(1)),
"/api/orders", Bandwidth.simple(120,Duration.ofMinutes(1))
);
7. Returning Rate Limit Headers (X-RateLimit-*)
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Max requests per window |
X-RateLimit-Remaining |
Tokens left |
X-RateLimit-Reset |
Epoch seconds when refilled |
Retry-After |
Seconds to wait (on 429) |
8. Handling Rate Limit Exceeded (429 Too Many Requests)
Example: Respond with 429 Too Many Requests
res.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
res.setContentType("application/problem+json");
res.getWriter().write("""
{"type":"https://example.com/rate-limit","title":"Too Many Requests","status":429}
""");
9. Implementing Sliding Window Algorithm
| Algorithm | Pros / Cons |
|---|---|
| Fixed window | Simple; burst at edges |
| Sliding log | Precise; high memory |
| Sliding window counter | Approximate; low memory (good default) |
| Token bucket | Allows bursts up to capacity |
| Leaky bucket | Smooth output rate |