Published 2026-07-18 by Shubham Bhati — Backend Engineer (Java 17, Spring Boot, Microservices).
By Shubham Bhati, Backend Engineer
It starts with a sudden spike in your dashboard. A rogue script or a brute-force attack hammers your endpoint, spikes your CPU to 99% and crashes your database. Every backend engineer has faced this nightmare. To protect your services, you need a defense mechanism. This is where spring boot rate limiting becomes critical. Instead of letting traffic spikes take down your entire infrastructure, you can implement token-bucket throttling. Using Bucket4j combined with Redis, you can build a distributed, high-performance rate limiter that keeps your microservices alive under heavy load.
Why Bucket4j and Redis?
Many developers start with simple in-memory rate limiting. While memory-based buckets work for single-instance applications, they fail instantly in microservices. When you scale your Spring Boot app behind a load balancer, instances do not share state. A client could bypass your limit by hitting different pods.
To solve this, we combine Bucket4j with Redis. Redis acts as a centralized state store, ensuring that every API node queries the same token bucket count. This setup prevents api throttling bypasses across your cluster. Using the redis rate limit strategy with Bucket4j ensures high performance because Redis executes state checks in-memory with sub-millisecond latency.
To configure this, we use the bucket4j-redis extension. It uses Redisson or Lettuce under the hood to execute atomic Redis commands. This guarantees thread safety without locking your Spring application threads, keeping your p99 latency extremely low. Because Redis operations are single-threaded and atomic, we avoid race conditions when two concurrent requests try to consume tokens at the exact same millisecond.
Step-by-Step Configuration
Let us set up the configuration. First, add the Bucket4j and Redis dependencies to your pom.xml. Then, configure the Redis connection in your application.yml file. Here is how your configuration looks in this basic bucket4j tutorial setup:
spring:
data:
redis:
host: localhost
port: 6379
timeout: 2000
cache:
type: redis
bucket4j:
enabled: true
filters:
- cache-name: rate-limit-buckets
url: /api/v1/resources/.*
rate-limits:
- bandwidths:
- capacity: 100
time: 1
unit: minutes
This setup defines a bucket with a capacity of 100 tokens that refills every minute. Next, create a configuration class to bind Bucket4j with your Redis cache manager. This initialization hooks into your existing Spring Boot cache configuration and sets up the distributed bucket state.
Using this declarative approach keeps your controller code clean. It applies the rate limit filter directly to your HTTP request lifecycle before it even reaches your controller layer.
Implementing the Rate Limiting Filter
For granular control, you can implement a custom Spring WebFilter or Interceptor. This is ideal when you need to rate limit based on client IP addresses, API keys or JWT claims rather than a global endpoint limit. Here is a practical OncePerRequestFilter implementation:
@Component
public class RateLimitingFilter extends OncePerRequestFilter {
private final ProxyManager<String> proxyManager;
public RateLimitingFilter(ProxyManager<String> proxyManager) {
this.proxyManager = proxyManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String ip = request.getRemoteAddr();
BucketConfiguration config = BucketConfiguration.builder()
.addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1))))
.build();
Bucket bucket = proxyManager.builder().build(ip, () -> config);
if (bucket.tryConsume(1)) {
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too many requests. Please try again later.");
}
}
}
Using this programmatic approach allows you to dynamically adjust limits based on user roles. For instance, premium users can have higher limits while anonymous requests get heavily throttled. By handling this check in the filter chain, you ensure your database connection pool is never exhausted by abusive traffic.
Common Pitfalls
- Neglecting Redis Connection Pool Tuning: High-throughput rate limiting creates massive traffic to Redis. If your HikariCP or Lettuce connection pool is too small, you will face thread starvation and increased latency. Always monitor your pool size and tune connection timeouts.
- Using Too Many Ephemeral Keys: Creating a bucket for every unique IP address can bloat your Redis memory. If you have millions of unique bots scanning your site, configure an aggressive Time-To-Live (TTL) on your cache keys to clean up expired buckets automatically.
- Insecure Client Identification: Relying solely on the
X-Forwarded-Forheader for IP identification is dangerous. Clients can easily spoof this header to bypass your limits. Always validate headers behind a trusted reverse proxy like Nginx or Cloudflare. - Lack of Graceful Fallbacks: If your Redis cluster goes down, your rate limiter should fail open, not closed. Build fallback mechanisms so your application continues to serve traffic even if the cache layer becomes temporarily unavailable.
Conclusion
Implementing spring boot rate limiting using Bucket4j and Redis is a reliable way to protect your production APIs from abuse. By moving bucket state to Redis, you secure your microservices while maintaining low latency. Start by applying basic IP throttling to your most critical endpoints and monitor your Redis CPU usage. Your servers will thank you.
Further Reading
Written by **Shubham Bhati* — Backend Engineer at AlignBits LLC, specializing in Java 17, Spring Boot, microservices, and AI integration. Connect on LinkedIn, GitHub, or read more at shubh2-0.github.io.*
Top comments (0)