Limitly is a production-grade, zero-config, annotation-based rate limiting library built for Spring Boot applications powered by Redis. Built with Java 21, Spring Boot 3, and Lua Scripting to ensure race-condition-free, atomic operations across distributed pod replicas.
- Annotation-based DX: Drop
@RateLimiton any@RestControllerendpoint. - Atomic Redis Execution: Zero race conditions under high concurrent traffic using Redis Lua scripts (
EVALSHA/EVAL). - Dual Algorithms Supported:
- Token Bucket: Smoothed traffic flow, configurable refill rates, low Redis memory footprint.
- Sliding Window Log: Exact request-count precision using Redis Sorted Sets (
ZSET).
- Flexible Key Resolution:
USER: Resolves user Principal orX-User-IDheader.IP: Resolves real client IP (aware ofX-Forwarded-Forproxy headers).SPEL: Dynamic SpEL expression support (e.g.#request.remoteAddr,#user.id).
- Resilient Fallback Modes:
FAIL_OPEN: (Default) Allows request traffic through when Redis is unreachable.FAIL_CLOSED: Throttles requests securely during Redis outages.
- Production Observability: Built-in Micrometer metrics (
ratelimiter.requests.allowed,ratelimiter.requests.denied,ratelimiter.redis.latency,ratelimiter.redis.failures) ready for Prometheus and Grafana dashboards. - RFC-compliant HTTP Headers: Automatically appends
X-RateLimit-Limit,X-RateLimit-Remaining, andRetry-AfterHTTP headers on throttled 429 responses.
┌───────────────────────────────────────────────────────────┐
│ Client Request │
└────────────────────────┬──────────────────────────────────┘
▼
┌─────────────────────┐
│ RateLimitInterceptor│ (HandlerInterceptor)
└──────────┬──────────┘
│ resolves key via KeyResolver
▼
┌─────────────────────┐
│ RateLimiterService │
└──────────┬──────────┘
│ executes Lua script atomically
▼
┌─────────────────────┐
│ Redis (Lua exec) │
└──────────┬──────────┘
│ Returns allowed/remaining/retryAfter
▼
┌───────────────────────────┐
│ Allowed: Continue (200) │
│ Denied: Return 429 + Retry│
└───────────────────────────┘
Add the starter dependency to your pom.xml:
<dependency>
<groupId>io.github.tharunprabu</groupId>
<artifactId>redis-rate-limiter-spring-boot-starter</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>spring:
data:
redis:
host: localhost
port: 6379
rate-limiter:
enabled: true
default-strategy: SLIDING_WINDOW
default-key-resolver: USER
fallback-mode: FAIL_OPEN
redis:
key-prefix: "ratelimit:"
response:
status: 429
body: '{"error": "rate_limit_exceeded", "message": "Too many requests. Please try again later."}'
include-retry-after-header: true
metrics:
enabled: true@RestController
@RequestMapping("/api")
public class ProductController {
// 5 requests per 10 seconds using Sliding Window Log
@GetMapping("/products/search")
@RateLimit(limit = 5, window = "10s", strategy = Algorithm.SLIDING_WINDOW)
public ResponseEntity<?> search(@RequestParam String query) {
return ResponseEntity.ok(Map.of("query", query));
}
// 10 requests per 1 minute based on IP address using Token Bucket algorithm
@PostMapping("/ads")
@RateLimit(key = "#request.remoteAddr", limit = 10, window = "1m", strategy = Algorithm.TOKEN_BUCKET)
public ResponseEntity<?> createAd(HttpServletRequest request) {
return ResponseEntity.ok("Ad created");
}
// Fail closed on critical resource if Redis connection drops
@GetMapping("/critical")
@RateLimit(limit = 2, window = "5s", onRedisFailure = FallbackMode.FAIL_CLOSED)
public ResponseEntity<?> criticalEndpoint() {
return ResponseEntity.ok("Access granted");
}
}| Feature | Token Bucket | Sliding Window Log |
|---|---|---|
| Data Structure | Redis Hash (HMSET) |
Redis Sorted Set (ZSET) |
| Memory Footprint | Extremely low (~2 fields per key) | Proportional to request volume in window |
| Accuracy | Continuous refill rate, allows micro-bursts | 100% exact request count precision |
| Best Used For | General API endpoints, bulk processing | Strict financial APIs, authentication/login attempts |
When Micrometer and Spring Actuator are present, Limitly automatically records the following metrics:
ratelimiter.requests.allowed(Counter, tagged withkey,route)ratelimiter.requests.denied(Counter, tagged withkey,route)ratelimiter.redis.latency(Timer measuring Redis Lua script execution duration)ratelimiter.redis.failures(Counter measuring Redis downtime fallback events)
limitly/
├── redis-rate-limiter-core/ # Core engine & Lua scripts (Zero Spring dependencies)
├── redis-rate-limiter-spring-boot-starter/ # Auto-Configuration, Interceptors, SpEL, Metrics
└── redis-rate-limiter-samples/demo-app/ # Executable Spring Boot sample application
Distributed under the Apache 2.0 License. Built by Tharun prabu.