A production-grade API rate limiter built with Java 21, Spring Boot 4.x, and Redis. FluxWard supports multiple rate limiting algorithms, per-route configuration, dynamic rule management, JWT-based client identification, circuit breaking, metrics, and abuse prevention.
HTTP Request
│
▼
RateLimitFilter (OncePerRequestFilter)
│── Block/Allow check → 403 / pass-through
│── Rate limit check
▼
RateLimiterService
│── RouteRuleResolver (client rule → dynamic route → YAML → global default)
│── KeyExtractorResolver (API_KEY / IP / JWT_SUBJECT)
│── CircuitBreakerRedisStore (Resilience4j wraps Redis calls)
│ └── AlgorithmFactory → TokenBucket / FixedWindow / SlidingWindow
│ └── RedisRateLimitStore (Lua scripts)
│── RateLimitMetrics (Micrometer counters + timers)
└── RateLimitEventPublisher (Spring events → async Redis storage)
- Three algorithms — Token Bucket, Fixed Window, Sliding Window Counter
- Per-route config — each route can override algorithm, capacity, windowMs, keyType
- Dynamic rules — update route/client rules at runtime via Admin API without restart
- Client-level overrides — specific clients can have custom rate limits
- JWT support — extract client identity from JWT subject claim
- Circuit breaker — Resilience4j wraps Redis calls, falls back to in-memory on failure
- Metrics — Micrometer counters and timers exposed at
/actuator/prometheus - Block/Allowlist — permanently block or whitelist clients
- Rate limit events — every decision stored in Redis, queryable via Admin API
- Health indicator — Redis status, circuit breaker state, and active rules at
/actuator/health - Configuration validation — fails fast on startup with clear error messages
- Java 21
- Redis 7+
- Maven 3.8+
# Start Redis
redis-server
# Run the app
mvn spring-boot:runmvn clean installAll options go under rate-limiter in application.yml:
rate-limiter:
capacity: 100 # global token capacity
refill-rate-per-second: 10 # tokens refilled per second (token bucket)
window-ms: 60000 # window size in milliseconds
fail-open: true # allow requests when Redis is down
key-type: API_KEY # API_KEY | IP | JWT_SUBJECT
algorithm: TOKEN_BUCKET # TOKEN_BUCKET | FIXED_WINDOW | SLIDING_WINDOW
jwt:
secret: "your-secret-key-32-chars-min"
routes:
- path: /api/login/**
algorithm: FIXED_WINDOW
capacity: 10
window-ms: 60000
key-type: API_KEY
- path: /api/search/**
algorithm: SLIDING_WINDOW
capacity: 30
window-ms: 10000
key-type: JWT_SUBJECT
admin:
username: admin
password: your-admin-password
resilience4j:
circuitbreaker:
instances:
redis:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 10s
permitted-number-of-calls-in-half-open-state: 3
sliding-window-type: COUNT_BASED
management:
endpoints:
web:
exposure:
include: health, prometheus, ratelimiter
endpoint:
health:
show-details: always| Field | Type | Default | Description |
|---|---|---|---|
capacity |
int | 100 | Max tokens / requests per window |
refill-rate-per-second |
double | 10 | Token refill rate (token bucket only) |
window-ms |
long | 60000 | Window size in milliseconds |
fail-open |
boolean | true | Allow traffic when Redis is down |
key-type |
string | API_KEY | Client identification method |
algorithm |
enum | TOKEN_BUCKET | Rate limiting algorithm |
jwt.secret |
string | — | HMAC-SHA256 secret (min 32 chars) |
| Method | Path | Description |
|---|---|---|
| GET | /test |
Token bucket (global default) |
| GET | /test/fixedwindow |
Fixed window |
| GET | /test/slidingwindow |
Sliding window (JWT auth) |
Headers:
X-API-Key: your-api-key
Authorization: Bearer <jwt-token> # for JWT routes
Response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1234567890
429 response:
{"error": "rate_limit_exceeded", "retryAfterMs": 1000}403 response (blocked):
{"error": "client_blocked", "clientKey": "test123"}All admin endpoints require Basic Auth (admin / your password).
| Method | Path | Description |
|---|---|---|
| PUT | /admin/routes/{path} |
Save dynamic route rule |
| GET | /admin/routes |
List all dynamic route paths |
| GET | /admin/routes/{path} |
Get rule for a path |
| DELETE | /admin/routes/{path} |
Delete dynamic route rule |
PUT /admin/routes/{path} request body:
{
"algorithm": "FIXED_WINDOW",
"capacity": 10,
"windowMs": 60000,
"refillRatePerSecond": 1.0,
"keyType": "API_KEY"
}| Method | Path | Description |
|---|---|---|
| PUT | /admin/clients/{clientKey} |
Save client-specific rule |
| GET | /admin/clients/{clientKey} |
Get client rule |
| DELETE | /admin/clients/{clientKey} |
Delete client rule |
| Method | Path | Description |
|---|---|---|
| POST | /admin/block/{clientKey} |
Block a client (returns 403) |
| DELETE | /admin/block/{clientKey} |
Unblock a client |
| GET | /admin/blocked |
List all blocked clients |
| POST | /admin/allow/{clientKey} |
Allowlist a client (bypasses rate limiting) |
| DELETE | /admin/allow/{clientKey} |
Remove from allowlist |
| GET | /admin/allowed |
List all allowlisted clients |
| Method | Path | Description |
|---|---|---|
| GET | /admin/events?limit=50 |
Get recent events |
| GET | /admin/events/{clientKey} |
Get events for a client |
| DELETE | /admin/events |
Clear all events |
Event response:
[
"{\"clientKey\":\"test123\",\"route\":\"/test\",\"algorithm\":\"TOKEN_BUCKET\",\"type\":\"ALLOWED\",\"timestamp\":\"2026-05-05T10:00:00Z\"}"
]| Path | Description |
|---|---|
/actuator/health |
App health with Redis, circuit breaker, and rule counts |
/actuator/prometheus |
Micrometer metrics in Prometheus format |
/actuator/ratelimiter |
Current rate limiter config per route |
- Tokens refill continuously at
refillRatePerSecond - Allows bursting up to
capacity - Best for: general API rate limiting with burst tolerance
- Hard counter reset at each window boundary
- Simple and predictable
- Best for: login endpoints, strict per-minute limits
- Note: allows 2x burst at window boundary
- Blends current and previous window counts by elapsed time
- Smoother than fixed window, no boundary burst
- Best for: search endpoints, smooth traffic shaping
For every request, FluxWard resolves the rule in this order:
- Client-specific rule —
fluxward:client:{clientKey}in Redis - Dynamic route rule —
fluxward:route:{path}in Redis (exact match) - YAML route rule —
rate-limiter.routes(Ant-path match) - Global default —
rate-limiter.*global config
| Phase | Features |
|---|---|
| Phase 1 | Token bucket algorithm, Redis Lua script, in-memory fallback, API key extraction |
| Phase 2 | Fixed window + sliding window algorithms, per-route config, algorithm strategy pattern |
| Phase 3 | Micrometer metrics, Resilience4j circuit breaker, JWT extraction, per-route key type, custom actuator endpoint |
| Phase 4 | Admin API, dynamic route/client rules, Basic Auth security |
| Phase 5 | Block/allowlist, rate limit events, async event listener, events admin API |
| Phase 6 | Health indicator, configuration validation, README |
