java 151 lines · 4 tabs

Per-Client Token Bucket Rate Limiting with a Spring Boot HandlerInterceptor

Shared by codesnips Aug 2026
4 tabs
package com.example.ratelimit;

public class TokenBucket {

    private final long capacity;
    private final long refillTokens;
    private final long refillIntervalNanos;

    private double availableTokens;
    private long lastRefillNanos;

    public TokenBucket(long capacity, long refillTokens, long refillIntervalNanos) {
        this.capacity = capacity;
        this.refillTokens = refillTokens;
        this.refillIntervalNanos = refillIntervalNanos;
        this.availableTokens = capacity;
        this.lastRefillNanos = System.nanoTime();
    }

    public synchronized boolean tryConsume() {
        refill();
        if (availableTokens >= 1.0) {
            availableTokens -= 1.0;
            return true;
        }
        return false;
    }

    public synchronized long remaining() {
        refill();
        return (long) Math.floor(availableTokens);
    }

    private void refill() {
        long now = System.nanoTime();
        long elapsed = now - lastRefillNanos;
        if (elapsed < refillIntervalNanos) {
            return;
        }
        long intervals = elapsed / refillIntervalNanos;
        double added = intervals * (double) refillTokens;
        availableTokens = Math.min(capacity, availableTokens + added);
        lastRefillNanos += intervals * refillIntervalNanos;
    }
}
4 files · java Explain with highlit

Rate limiting protects an API from abusive or runaway clients by capping how many requests each caller may make in a window. This snippet implements the classic token bucket algorithm and wires it into Spring's request pipeline so throttling happens before controller code runs.

In TokenBucket, each bucket holds a capacity, a refillTokens rate, and a refillIntervalNanos. The core method tryConsume is synchronized so concurrent requests from the same client see a consistent view of the token count. Before consuming, refill computes how many whole intervals elapsed since lastRefillNanos and adds tokens back, clamped to capacity. Using System.nanoTime() avoids wall-clock jumps from NTP adjustments. The bucket is intentionally simple and stateful — one instance per client — which is why it must be guarded against races.

RateLimiterService owns the map from client key to bucket. It uses a ConcurrentHashMap and computeIfAbsent so a bucket is lazily created exactly once per key, even under concurrent first-touch. The resolveKey helper derives the client identity from an X-API-Key header, falling back to the remote address, so anonymous callers are still bucketed. Buckets are configured from injected properties, keeping capacity and refill tunable without code changes.

RateLimitInterceptor implements HandlerInterceptor and runs in preHandle, the earliest hook that can short-circuit a request by returning false. When a token is available it sets informational X-RateLimit-* headers and lets the request proceed; when the bucket is empty it writes a 429 Too Many Requests with a Retry-After header and returns false, so the controller is never invoked. Emitting Retry-After lets well-behaved clients back off instead of hammering.

WebConfig registers the interceptor through WebMvcConfigurer, scoping it with addPathPatterns so only /api/** is throttled while health checks and static assets are exempt. A key trade-off is that this in-memory approach is per-instance: behind a load balancer each node has its own buckets, so a distributed deployment would swap RateLimiterService for a shared store like Redis. For a single service or coarse protection, this design is fast, dependency-free, and easy to reason about.


Related snips

Share this code

Here's the card — post it anywhere.

Per-Client Token Bucket Rate Limiting with a Spring Boot HandlerInterceptor — share card
Link copied