DEV Community

Shubham Bhati
Shubham Bhati

Posted on

Rate Limiting in Spring Boot REST APIs: Bucket4j + Redis

Spring Boot Rate Limiting

Published 2026-07-17 by Shubham Bhati — Backend Engineer (Java 17, Spring Boot, Microservices).

It is 3 AM. Your database CPU spikes to 99% because an unauthenticated bot is scraping your public search endpoint. A single rogue client has degraded your entire microservice cluster. If you have faced this, you know why basic security is not enough. Implementing spring boot rate limiting is the most effective way to protect your resources from abuse. By combining the token bucket algorithm of Bucket4j with the distributed state of Redis, we can build an api throttling mechanism that scales horizontally.

Why Bucket4j and Redis?

In-memory rate limiting works for single-instance apps. However, modern microservices run on multiple nodes behind a load balancer. If we keep the token bucket state in local JVM memory, users can bypass limits by hitting different instances. This is where a distributed redis rate limit strategy saves the day.

Bucket4j is a mature Java rate-limiting library based on the token bucket algorithm. Instead of reinventing the wheel, we offload the shared state to Redis. This ensures that regardless of which Spring Boot instance handles the HTTP request, the rate limit is enforced globally.

For production, latency is critical. By utilizing Redis with the Lettuce driver and pipelining, the state lookup takes less than 2 milliseconds, keeping your services fast.

Configuring Redis and Bucket4j: A Quick Bucket4j Tutorial

Let's set up our configuration. This bucket4j tutorial demonstrates how to configure the Lettuce connection pool and bucket sizes in your application.yml to prevent connection starvation under heavy load:

spring:
  data:
    redis:
      host: localhost
      port: 6379
      lettuce:
        pool:
          max-active: 50
          max-idle: 10
          max-wait: 1000ms
rate-limit:
  capacity: 100
  refill-tokens: 10
  refill-duration: 1s
Enter fullscreen mode Exit fullscreen mode

Using a dedicated connection pool for rate limiting is critical. If your main database queries block, you do not want your Lettuce pool to starve. By setting a strict timeout of 1000ms, we ensure that if Redis becomes unresponsive, the API fails open rather than blocking worker threads and taking down the service.

Implementing the Rate Limiting Filter

To intercept incoming requests, we write a Spring Boot OncePerRequestFilter. This filter extracts the client identifier, resolves the bucket from Redis and decides whether to block the request.

@Component
public class RateLimitFilter extends OncePerRequestFilter {
    @Autowired
    private ProxyManager<String> proxyManager;

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws ServletException, IOException {
        String ip = req.getRemoteAddr();
        BucketConfiguration config = BucketConfiguration.builder()
            .addLimit(limit -> limit.capacity(100).refillGreedy(10, Duration.ofSeconds(1)))
            .build();
        Bucket bucket = proxyManager.builder().build(ip, () -> config);
        if (bucket.tryConsume(1)) {
            chain.doFilter(req, res);
        } else {
            res.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            res.getWriter().write("Too many requests.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In production, relying solely on IP addresses can be problematic. Use a combination of API keys, JWT user IDs and IP addresses to uniquely identify clients.

Production Considerations: Monitoring and P99 Latency

Running rate limiting in production requires strict observability. Because Bucket4j executes commands on Redis via EVAL scripts, it operates atomically. However, these Redis operations still incur network overhead.

To keep your p99 latency under control, run Redis on the same private network as your Spring Boot services. We use Micrometer to export rate limiting metrics. Monitoring the bucket4j.consumption.rate and tracking Redis latency tells us if our connection pool is sized correctly.

Keep an eye on memory footprint. Each bucket in Redis occupies around 200 bytes. If you have 5 million active users, that translates to 1 GB of RAM. Configure an eviction policy like volatile-lru so Redis does not crash if memory limits are breached.

Common Pitfalls

  • Failing Closed: If Redis goes down, your rate limiter should fail open to keep the service running.
  • Ignoring Clock Drift: Redis cluster nodes must be synchronized using NTP. Bucket4j relies on timestamps, so drift leads to unpredictable throttling.
  • Bad Client Identification: Using only the X-Forwarded-For header without validation allows attackers to spoof their IP address.
  • No Redis TTLs: Without expiration times on keys holding bucket states, old client data remains in memory forever.

Conclusion

Securing your Spring Boot REST APIs does not require complex infrastructure. By using Bucket4j and Redis, you get a distributed, high-performance rate-limiting solution capable of handling millions of requests. It protects your databases, reduces costs and keeps your services running during traffic spikes. Implement it today before your next production incident.


Spring Boot Rate Limiting in production

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)