Protect your REST APIs from abuse using the Token Bucket Algorithm, implemented with Bucket4j and distributed via Redis in Spring Boot 3.x.
This example demonstrates a production-ready implementation of API rate limiting using Spring Boot 3.x, Bucket4j, and Redis.
Why it's important: Rate limiting protects your system from DDoS attacks, brute-force login attempts, and general abuse by malicious actors or misconfigured scripts. It's a critical component in System Design interviews and real-world microservices.
Bucket4j uses the Token Bucket Algorithm. Imagine a bucket that holds a maximum number of tokens (capacity). Tokens are added to the bucket at a fixed rate (refill rate). Every time a request comes in, it must take a token from the bucket. If the bucket is empty, the request is rejected (HTTP 429 Too Many Requests). This handles both steady traffic and short bursts smoothly.
Add the required dependencies for Spring Web, Redis (Lettuce), and Bucket4j's Redis integration.
For a distributed system (multiple instances of your Spring Boot app), in-memory rate limiting fails because each instance has its own bucket. Redis solves this by acting as a centralized datastore for bucket states.
Configure Bucket4j to use Redis as its backend. We use Lettuce (Spring's default Redis client) to create a generic `ProxyManager` that Bucket4j uses to interact with Redis.
This service handles the creation and retrieval of buckets for specific keys (e.g., IP addresses or User IDs). We define different policies based on the context (Public API vs User-specific API vs Admin API).
💡 Note on "refillGreedy": Greedy refill adds tokens continuously based on elapsed time (e.g., if refill rate is 60 tokens/minute, it adds 1 token per second). "refillIntervally" would add all 60 tokens exactly at the 1-minute mark.
The Filter intercepts all incoming HTTP requests. It identifies the client (by IP or User ID from JWT context), applies the rate limit logic, and either lets the request proceed or immediately rejects it with a `429 Too Many Requests` status, adding helpful retry headers.
Finally, define your REST endpoints. Note how clean the controllers are—they don't need to know anything about the rate limiting logic, as the Filter handles it transversally.
RateLimitFilterRateLimitServiceApiController → HTTP 200 OK1. Hitting `/api/limited` for the first time:
2. Hitting `/api/limited` for the 6th time within a minute: