Rate Limiting with Resilience4j: Token Bucket, Leaky Bucket, and Burst Control
Fast recall
Quick revision
Core answer
limitForPeriod (tokens per limitRefreshPeriod) and timeoutDuration (how long a caller waits for a token). Token bucket absorbs short bursts by allowing accumulated tokens to be spent at once; leaky bucket (e.g., Bucket4j's Bandwidth with smooth refill) smooths output to a constant rate. Use @RateLimiter(name = "svc", fallbackMethod = "degraded") for outgoing-call pacing; use Spring Cloud Gateway's `RequestRateLimiter` filter + Redis for distributed inbound rate limiting across replicas.Understanding of rate-limiting algorithms, per-instance vs distributed limits, and Resilience4j RateLimiter configuration
Assuming per-pod RateLimiter enforces a global rate (it doesn't; 5 pods at 100 rps = 500 rps globally); using rate limiter for DDoS defense (that's the gateway's job)
Contrast token bucket vs leaky bucket, explain why bursty traffic needs distinct configuration, and know when to reach for Bucket4j + Redis or Spring Cloud Gateway's RequestRateLimiter for distributed enforcement
Interview-ready guide
Interview answer
What a rate limiter does
- Caps the rate of calls through a protected section
- In Resilience4j it is the innermost resilience primitive — TimeLimiter, CircuitBreaker, Retry, Bulkhead all wrap around it
- Purpose: respect downstream quotas, avoid self-inflicted overload, smooth traffic to fragile backends
- Not a DDoS shield — that belongs at the gateway, WAF, or CDN
Algorithms to know
- Token bucket (Resilience4j default) — tokens refill periodically; tolerates bursts up to the cap
- Leaky bucket — constant output rate; smooths input bursts; Bucket4j implements this
- Fixed window — simple counter per minute; vulnerable to double-burst at window edges; avoid
- Sliding-window log — exact count over a rolling window; highest memory cost; use for billing-grade accuracy
Resilience4j configuration knobs
- limitForPeriod — tokens per refresh period
- limitRefreshPeriod — how often the bucket refills
- timeoutDuration — how long a caller waits for a token; zero means fail fast
- Token cap equals limitForPeriod — accumulation is bounded
The distributed trap
- Resilience4j RateLimiter is per-JVM
- Five pods at 100 per second allow 500 globally, not 100
- If you have a third-party quota at 100 per second, per-pod config silently breaks SLA
- Options: divide the limit by replica count (brittle), use Bucket4j plus Redis for shared state, or use a sidecar like Envoy's global rate-limit service
Spring Cloud Gateway with Redis for inbound traffic
- RequestRateLimiter filter plus RedisRateLimiter
- replenishRate plus burstCapacity gives token bucket across all gateway pods
- KeyResolver bean chooses the limit key — per user ID, per API key, or IP fallback
- Correct answer for multi-pod gateway fleets
Bucket4j for business quotas
- Per-API-key or per-tenant quotas with exact global enforcement
- Bandwidth plus Refill model; integrates with Redisson or Hazelcast for shared grid
- Use when contracts specify hard ceilings like 10,000 requests per hour
Handling rejection well
- Return HTTP 429 with a Retry-After header; naive clients otherwise retry immediately
- Log and metric rejections; sustained high rejection rate means either misconfig or legitimate growth
- Fallback options: queue for later, return stale data, 429 to client
Common pitfalls
- Confusing per-instance with global rate
- Using rate limiter for DDoS protection
- No burst cap — idle customer accumulates tokens and overwhelms downstream at wake-up
- No 429 + Retry-After — well-behaved clients retry in a loop and keep hitting the limit
- No burst tests — steady-state load tests miss bucket-drain scenarios
- Hot shard — one popular key saturates a single Redis thread
The strategic answer
- Token bucket for bursty-tolerant quota respect; leaky bucket when output must stay smooth
- Resilience4j for per-JVM outbound pacing; Gateway plus Redis for distributed inbound limits
- Bucket4j for per-tenant contractual quotas
- Always return 429 with Retry-After; always metric rejections; always burst-test the config
Rate Limiting with Resilience4j: Token Bucket, Leaky Bucket, and Burst Control
Direct Answer
Resilience4j RateLimiter enforces a per-instance token-bucket policy via limitForPeriod (tokens per limitRefreshPeriod) and timeoutDuration (how long a caller waits for a token). Token bucket absorbs short bursts by allowing accumulated tokens to be spent at once; leaky bucket (e.g., Bucket4j's Bandwidth with smooth refill) smooths output to a constant rate. Use @RateLimiter(name = "svc", fallbackMethod = "degraded") for outgoing-call pacing; use Spring Cloud Gateway's RequestRateLimiter filter + Redis for distributed inbound rate limiting across replicas.
Common Mistakes & Pitfalls
Assuming per-pod RateLimiter enforces a global rate (it doesn't; 5 pods at 100 rps = 500 rps globally); using rate limiter for DDoS defense (that's the gateway's job)
Check your understanding
Answer these without looking back at the article.
- How would you implement a truly global rate limit for a 100-rps third-party API across 5 Spring Boot replicas?
- What is the 'hot shard' problem with Redis-backed rate limiters and how do you mitigate it?
- When would you choose Bucket4j over Resilience4j's RateLimiter?
- How do you handle rate-limit rejections on the client side — retry behaviour with Retry-After?
Related interview prep categories
2Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.