In a world of public-facing APIs, rate limiting is essential to ensure fair usage, prevent abuse, and protect backend services from overload. Whether it’s a payment gateway or a public REST API, having control over how often a client can hit your service is a must-have.
This guide demonstrates how to use Bucket4j, a powerful Java library for API rate limiting, within a Spring Boot application. We will explore in-memory and distributed (Redis-based) setups, flexible rate control rules, and integration with standard Spring filters or AOP.

π§© What is Bucket4j?
Bucket4j is a Java library for token bucket-style rate limiting. It supports:
- In-memory and distributed backends (e.g., Redis, Hazelcast, JCache)
- Bandwidth control via refill rate, capacity, delay
- Thread-safety and zero dependencies
Itβs ideal for Spring Boot apps because of its simplicity, extensibility, and performance.
π¦ What is API Rate Limiting?
API rate limiting restricts how many requests a user, IP, or token can make in a given time window (e.g., 100 requests per minute). Common use cases include:
- Preventing abuse of public APIs
- Protecting backend systems from overload
- Enforcing fair usage among clients
- DDoS mitigation
π οΈ Project Setup
π Maven Dependencies (In-Memory Only)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<dependencies> <dependency> <groupId>com.github.vladimir-bukhtoyarov</groupId> <artifactId>bucket4j-core</artifactId> <version>8.6.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> |
βοΈ Configuration: Basic Rate Limiting Filter
Weβll start with a simple in-memory bucket that applies globally per IP address.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.kscodes.springboot.advanced.filter; import io.github.bucket4j.*; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import java.io.IOException; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component public class RateLimitingFilter implements Filter { private final Map<String, Bucket> buckets = new ConcurrentHashMap<>(); private Bucket createNewBucket() { Bandwidth limit = Bandwidth.classic(5, Refill.greedy(5, Duration.ofSeconds(60))); return Bucket.builder().addLimit(limit).build(); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) request; String ip = httpReq.getRemoteAddr(); Bucket bucket = buckets.computeIfAbsent(ip, k -> createNewBucket()); if (bucket.tryConsume(1)) { chain.doFilter(request, response); } else { HttpServletResponse httpResp = (HttpServletResponse) response; httpResp.setStatus(429); httpResp.getWriter().write("Too Many Requests - Try again later"); } } } |
π§ͺ Test Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.kscodes.springboot.advanced.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DemoController { @GetMapping("/api/data") public String getData() { return "You accessed protected data!"; } } |
βοΈ Customizing Rate Limits
You can fine-tune rate limits per:
Scope | Strategy |
---|---|
IP Address | Bucket per IP in a map |
User ID / Token | Extract from Authorization header and use as map key |
Endpoint Level | Use separate limits for different controllers or paths |
Example for multiple bandwidths:
1 2 3 4 5 |
Bandwidth limit = Bandwidth.classic(10, Refill.intervally(10, Duration.ofSeconds(60))) .withInitialTokens(5); |
ποΈ Redis-backed Bucket4j (For Distributed Rate Limiting)
Add Redis Dependencies:
1 2 3 4 5 6 7 8 9 10 11 12 |
<dependency> <groupId>io.github.bucket4j</groupId> <artifactId>bucket4j-redis</artifactId> <version>8.6.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> |
Then use RedisProxyManager
:
1 2 3 4 5 6 |
RedisBucketBuilder builder = Bucket4j.extension(redisExtension) .builder() .addLimit(bandwidth); |
Useful when your app runs on multiple nodes and must share usage counters globally.
π Monitor Rate Limit Usage (Optional)
You can log or expose metrics using:
bucket.getAvailableTokens()
- Spring Boot Actuator + Micrometer
- Custom metrics endpoint
π§Ό Best Practices
Practice | Reason |
---|---|
Use Redis for distributed setups | Ensures global token state across nodes |
Return proper HTTP status 429 | Clients can retry after cooldown |
Include retry headers | Use Retry-After headers to guide clients |
Avoid over-restricting | Too strict rate limits can block valid users |
Combine with auth tokens | Limit per user for authenticated APIs |
π Example: API Behavior
Request # | Within 1 Minute | Response |
---|---|---|
1 – 5 | Yes | 200 OK |
6+ | No | 429 Too Many Requests |
π Conclusion
Implementing Bucket4j API Rate Limiting in Spring Boot helps you scale your APIs safely while preserving system resources. Whether youβre defending against traffic spikes or enforcing client fairness, Bucket4j provides an elegant and efficient solution.
Start with an in-memory setup, and evolve to Redis or Hazelcast as your application scales horizontally.