Rate limiting and API throttling

David Kumar Jan 2026
3 tabs
package com.example.demo.interceptor;

import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Refill;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class RateLimitInterceptor implements HandlerInterceptor {

    private final Map<String, Bucket> cache = new ConcurrentHashMap<>();

    @Override
    public boolean preHandle(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler
    ) throws Exception {
        String key = getClientKey(request);
        Bucket bucket = resolveBucket(key);

        if (bucket.tryConsume(1)) {
            long remaining = bucket.getAvailableTokens();
            response.addHeader("X-Rate-Limit-Remaining", String.valueOf(remaining));
            return true;
        } else {
            long waitForRefill = bucket.estimateAbilityToConsume(1).getNanosToWaitForRefill() / 1_000_000_000;
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.addHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill));
            response.getWriter().write("Rate limit exceeded. Try again in " + waitForRefill + " seconds");
            return false;
        }
    }

    private Bucket resolveBucket(String key) {
        return cache.computeIfAbsent(key, k -> createNewBucket());
    }

    private Bucket createNewBucket() {
        // 100 requests per minute
        Bandwidth limit = Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1)));
        return Bucket.builder()
            .addLimit(limit)
            .build();
    }

    private String getClientKey(HttpServletRequest request) {
        String apiKey = request.getHeader("X-API-Key");
        if (apiKey != null) {
            return apiKey;
        }
        return request.getRemoteAddr();
    }
}
3 files · java Explain with highlit

Rate limiting prevents API abuse and ensures fair resource usage. I implement rate limiting using Bucket4j for token bucket algorithm or Redis for distributed scenarios. Limits apply per user, IP, or API key. HTTP 429 (Too Many Requests) indicates limit exceeded. Headers communicate remaining requests and reset time. Sliding window algorithms provide smooth rate limiting. Tiered limits support different user levels. Rate limiting protects against DoS attacks and resource exhaustion. Distributed systems use Redis to share rate limit state. Custom annotations simplify application. Graceful degradation prioritizes critical operations. Monitoring tracks rate limit hits. Proper rate limiting balances system protection with user experience.