Limitly is a production-grade, zero-config Spring Boot starter library for Redis-backed rate limiting. It guarantees atomic execution across distributed microservice pod replicas using Lua scripting.
If you are building and consuming Limitly locally across projects on your machine:
-
Clone and install Limitly into your local
~/.m2/repository:git clone https://github.com/onizukaTP/limitly.git cd limitly mvn clean install -
Add the dependency to your application's
pom.xml:<dependency> <groupId>io.github.tharunprabu</groupId> <artifactId>redis-rate-limiter-spring-boot-starter</artifactId> <version>0.1.0-SNAPSHOT</version> </dependency>
You can consume Limitly directly from GitHub releases or branches using JitPack:
-
Add the JitPack repository to your application's
pom.xml:<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
-
Add the Limitly dependency:
<dependency> <groupId>com.github.onizukaTP</groupId> <artifactId>limitly</artifactId> <version>master-SNAPSHOT</version> </dependency>
<dependency>
<groupId>io.github.tharunprabu</groupId>
<artifactId>redis-rate-limiter-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>Add these properties to your application.yml:
spring:
data:
redis:
host: localhost
port: 6379
rate-limiter:
# Master toggle to enable/disable rate limiting
enabled: true
# Default algorithm strategy across endpoints if unspecified: SLIDING_WINDOW | TOKEN_BUCKET
default-strategy: SLIDING_WINDOW
# Default key resolver: USER | IP | SPEL
default-key-resolver: USER
# Behavior when Redis is down/unreachable: FAIL_OPEN | FAIL_CLOSED
fallback-mode: FAIL_OPEN
redis:
# Prefix for all Redis keys created by Limitly
key-prefix: "ratelimit:"
response:
# HTTP status returned when rate limit is breached (default: 429)
status: 429
# JSON body returned to throttled clients
body: '{"error": "rate_limit_exceeded", "message": "Too many requests. Please try again later."}'
# Whether to return the standard 'Retry-After' header (seconds remaining)
include-retry-after-header: true
metrics:
# Enable Micrometer metrics collection
enabled: trueIdeal for security-sensitive endpoints (logins, payment APIs, password resets).
@GetMapping("/api/login")
@RateLimit(limit = 5, window = "1m", strategy = Algorithm.SLIDING_WINDOW)
public ResponseEntity<?> login() {
return ResponseEntity.ok("Logged in");
}Ideal for standard REST APIs, data ingestion, and file downloads.
@GetMapping("/api/posts")
@RateLimit(limit = 100, window = "1m", strategy = Algorithm.TOKEN_BUCKET)
public ResponseEntity<?> getPosts() {
return ResponseEntity.ok(postService.findAll());
}Resolve rate limits per IP, user ID, or request attributes using Spring Expression Language:
// Rate limit by client remote IP address
@PostMapping("/api/comments")
@RateLimit(key = "#request.remoteAddr", limit = 10, window = "30s")
public ResponseEntity<?> postComment(HttpServletRequest request) {
return ResponseEntity.ok("Comment posted");
}If Redis goes down, FAIL_CLOSED denies traffic to protect sensitive backend systems.
@PostMapping("/api/payment/checkout")
@RateLimit(limit = 3, window = "10s", onRedisFailure = FallbackMode.FAIL_CLOSED)
public ResponseEntity<?> checkout() {
return ResponseEntity.ok("Payment processed");
}Limitly integrates out-of-the-box with Micrometer, Prometheus, and Spring Boot Actuator.
| Metric Name | Type | Description |
|---|---|---|
ratelimiter.requests.allowed |
Counter | Total requests that passed rate limits |
ratelimiter.requests.denied |
Counter | Total requests throttled (429) |
ratelimiter.redis.latency |
Timer | Execution duration of Redis Lua scripts |
ratelimiter.redis.failures |
Counter | Count of Redis connectivity fallback events |
The window attribute in @RateLimit supports standard shorthand duration strings:
"500ms"(500 milliseconds)"10s"(10 seconds)"5m"(5 minutes)"1h"(1 hour)"1d"(1 day)- Standard ISO-8601 strings (e.g.
"PT1M")