A basic HTTP Rate Limiter built with Spring Boot and Redis. Implements the Token Bucket algorithm to control request rates per client, returning HTTP 429 Too Many Requests when limits are exceeded.
HTTP Request
│
▼
┌─────────────────────────────┐
│ RateLimiterFilter │ ← OncePerRequestFilter (runs on every request)
│ • Resolves client identity │
│ • Calls isAllowed() │
└───────────┬─────────────────┘
│ allowed?
┌──────┴────────┐
│ YES │ NO
▼ ▼
Forward HTTP 429
Request (ErrorResponse)
│
▼
┌──────────────────────────────┐
│ RedisTokenBucketService │
│ • refillTokens() │ ← Calculates elapsed time, adds tokens
│ • isAllowed() │ ← Atomically checks & decrements token
│ • getTokenCount() │ ← Read-only inspection
└──────────────┬───────────────┘
│
▼
Redis (Jedis)
rate_limiter:tokens:<clientId>
rate_limiter:last_refill:<clientId>
| Parameter | Default | Description |
|---|---|---|
capacity |
10 | Max tokens per client (burst size) |
refillRate |
5 | Tokens added per second |
How it works:
- Each client gets a virtual bucket of tokens (max = capacity)
- Every accepted request consumes 1 token
- Tokens refill at
refillRateper second up tocapacity - When the bucket is empty →
HTTP 429 Too Many Requests - On Redis failure → fail-open (requests are allowed)
The filter resolves client identity in priority order:
X-Client-Idrequest header (explicit client ID)X-Forwarded-Forheader (set by load balancers/proxies)- TCP remote IP address (direct connections)
- Java 17+
- Maven 3.8+
- Docker & Docker Compose (for Redis)
cd RateLimiter-sb
docker-compose up -d./mvnw spring-boot:runThe server starts on http://localhost:8080
Edit src/main/resources/application.properties:
# Token Bucket configuration
rate-limiter.capacity=10 # Burst size (max tokens)
rate-limiter.refill-rate=5 # Tokens added per second
# Redis connection
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=2000GET /api/rate-limit/status?clientId={clientId}
Returns current token state for a client without consuming a token.
Response (200 OK):
{
"clientId": "127.0.0.1",
"tokensRemaining": 8,
"capacity": 10,
"refillRatePerSecond": 5,
"allowed": true,
"message": "Requests are currently permitted."
}GET /actuator/health
When a client exceeds their limit, all requests receive:
Response (429 Too Many Requests):
{
"timestamp": "2026-04-19T10:00:00Z",
"status": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please slow down and try again later.",
"clientId": "127.0.0.1"
}# Fire 10 requests — all should succeed (HTTP 200)
for i in {1..10}; do
curl -s -o /dev/null -w "Request $i: %{http_code}\n" \
-H "X-Client-Id: test-user" \
http://localhost:8080/api/rate-limit/status
done
# 11th request — should return 429
curl -v -H "X-Client-Id: test-user" http://localhost:8080/api/rate-limit/status
# Check token count without consuming
curl http://localhost:8080/api/rate-limit/status?clientId=test-user./mvnw testsrc/main/java/com/RateLimiter/RateLimiter_sb/
├── RateLimiterSbApplication.java # Spring Boot entry point
├── config/
│ ├── RateLimiterProperties.java # Rate limit config binding
│ ├── RedisProperties.java # Jedis pool factory
│ └── SecurityConfig.java # Disables default HTTP Basic auth
├── dto/
│ ├── RateLimitStatusResponse.java # Status endpoint response
│ └── ErrorResponse.java # 429 error response body
├── filter/
│ └── RateLimiterFilter.java # OncePerRequestFilter — enforcement
├── controller/
│ └── RateLimiterController.java # /api/rate-limit/status endpoint
└── services/
└── RedisTokenBucketService.java # Token bucket logic (Redis-backed)