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;
}
}
package com.example.ratelimit;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Service
public class RateLimiterService {
private final ConcurrentHashMap<String, TokenBucket> buckets = new ConcurrentHashMap<>();
private final long capacity;
private final long refillTokens;
private final long refillIntervalNanos;
public RateLimiterService(
@Value("${ratelimit.capacity:60}") long capacity,
@Value("${ratelimit.refill-tokens:60}") long refillTokens,
@Value("${ratelimit.refill-seconds:60}") long refillSeconds) {
this.capacity = capacity;
this.refillTokens = refillTokens;
this.refillIntervalNanos = TimeUnit.SECONDS.toNanos(refillSeconds);
}
public TokenBucket resolveBucket(HttpServletRequest request) {
return buckets.computeIfAbsent(resolveKey(request),
key -> new TokenBucket(capacity, refillTokens, refillIntervalNanos));
}
public long capacity() {
return capacity;
}
private String resolveKey(HttpServletRequest request) {
String apiKey = request.getHeader("X-API-Key");
if (StringUtils.hasText(apiKey)) {
return "key:" + apiKey;
}
return "ip:" + request.getRemoteAddr();
}
}
package com.example.ratelimit;
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;
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
private final RateLimiterService rateLimiterService;
public RateLimitInterceptor(RateLimiterService rateLimiterService) {
this.rateLimiterService = rateLimiterService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
TokenBucket bucket = rateLimiterService.resolveBucket(request);
response.setHeader("X-RateLimit-Limit", String.valueOf(rateLimiterService.capacity()));
if (bucket.tryConsume()) {
response.setHeader("X-RateLimit-Remaining", String.valueOf(bucket.remaining()));
return true;
}
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setHeader("X-RateLimit-Remaining", "0");
response.setHeader("Retry-After", "1");
response.setContentType("application/json");
try {
response.getWriter().write("{\"error\":\"rate limit exceeded\"}");
} catch (java.io.IOException ignored) {
// client disconnected; nothing further to write
}
return false;
}
}
package com.example.ratelimit;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final RateLimitInterceptor rateLimitInterceptor;
public WebConfig(RateLimitInterceptor rateLimitInterceptor) {
this.rateLimitInterceptor = rateLimitInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/health");
}
}
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
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
Share this code
Here's the card — post it anywhere.