DEV Community

Shubham Bhati
Shubham Bhati

Posted on

Rate Limiting in Spring Boot REST APIs: Bucket4j + Redis

Spring Boot Rate Limiting

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

Picture this: it is 3 AM and your on-call alarm screams. A rogue script is hammering your SMS gateway endpoint, spiking your API costs and choking your database connections. Standard IP blocking is too blunt and hurts legitimate users behind NATs. You need a surgical way to throttle traffic.

This is where spring boot rate limiting becomes critical. By combining Bucket4j with Redis, you can build a distributed, high-performance rate limiting system that stops API abuse without degrading your application's p99 latency. Let us look at how to implement this pattern.

Why Pair Bucket4j with Redis?

Bucket4j is a Java library based on the Token Bucket algorithm. It is incredibly fast and memory-efficient. However, in a microservices environment, keeping bucket states in local JVM memory fails. If you scale your service to five pods behind a load balancer, a user can bypass their limit by hitting different instances.

To solve this, we back Bucket4j with Redis. Redis acts as a centralized, fast in-memory data store where bucket tokens are shared across all Spring Boot instances. Using a redis rate limit setup ensures that state is synchronized instantly.

For communication, Redisson is highly recommended for Bucket4j. It provides out-of-the-box integration with bucket locks, preventing race conditions when concurrent requests attempt to consume tokens from the same bucket simultaneously. Under the hood, Bucket4j executes Lua scripts on Redis to keep token consumption atomic, keeping your network overhead to a bare minimum.

Setting Up the Configuration

First, let us pull in the required dependencies. We need the Bucket4j core library, the Redis integration module and the Redisson client. Avoid using bloated starter libraries; manual configuration gives you complete control over your pool sizes and timeouts.

Here is your Maven setup:

<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>8.10.1</version>
</dependency>
<dependency>
    <groupId>com.bucket4j</groupId>
    <artifactId>bucket4j-redis</artifactId>
    <version>8.10.1</version>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.27.2</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Next, configure your Redis connection pool in application.yml. Pay close attention to timeout settings to prevent thread starvation under heavy load:

spring:
  data:
    redis:
      host: localhost
      port: 6379
      timeout: 200ms
      lettuce:
        pool:
          max-active: 50
          max-idle: 10
          max-wait: 500ms
Enter fullscreen mode Exit fullscreen mode

Under heavy load, Redis command latency directly impacts your API response times. Setting an aggressive connection timeout (like 200ms) prevents slow Redis queries from exhausting your HikariCP database pool.

Implementing the Rate Limiting Filter

To intercept incoming REST requests, we implement a Spring Web servlet filter. This ensures that blocked requests are rejected early in the filter chain, saving CPU cycles and database connections from executing business logic.

Here is a lightweight OncePerRequestFilter implementation using our bucket4j tutorial approach:

@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 apiKey = request.getHeader("X-API-KEY");
        if (apiKey == null) {
            response.sendError(HttpStatus.BAD_REQUEST.value(), "Missing API Key");
            return;
        }

        BucketConfiguration config = BucketConfiguration.builder()
            .addLimit(limit -> limit.capacity(100).refillGreedy(100, Duration.ofMinutes(1)))
            .build();

        Bucket bucket = proxyManager.builder().build(apiKey, config);

        if (bucket.tryConsume(1)) {
            filterChain.doFilter(request, response);
        } else {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("Too many requests - Slow down!");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This setup enforces precise api throttling. When a user hits their limit, Bucket4j blocks the call instantly at the network edge.

Common Pitfalls to Avoid

When implementing distributed rate limiting, developers often make mistakes that lead to production outages:

  • Not configuring a fallback strategy: If Redis goes down, your API should not fail. Implement a try-catch block around the rate limiter that fails open (allowing requests through) and triggers an alert.
  • Using client IP as the sole key: Clients behind corporate NATs or proxies share the same IP. Rate limiting purely by IP will accidentally throttle thousands of legitimate users. Use authenticated API keys or JWT claims instead.
  • Ignoring Redis Key TTL: If you do not configure an expiration time on your rate limit keys in Redis, your memory usage will grow indefinitely as new users sign up, eventually causing Redis to run out of memory.
  • Blocking I/O on Redis threads: Ensure your Redis client connection pool is sized correctly. A slow Redis connection pool can starve your Tomcat and HikariCP threads, dragging down your entire service.

Conclusion

Securing your Spring Boot APIs against brute force attacks and abusive traffic is non-negotiable. By combining the speed of Bucket4j with the distributed power of Redis, you create a highly reliable, low-overhead rate limiter. Start small, monitor your Redis memory footprint and always have a fallback plan to keep your application resilient under heavy load.


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)