DEV Community

Cover image for Fixed Window Rate Limiting: Concept, Examples, and Java Implementation

Fixed Window Rate Limiting: Concept, Examples, and Java Implementation

πŸ“Œ What Is Fixed Window Rate Limiting?

Fixed Window Rate Limiting is a straightforward algorithm that controls request rates by dividing time into fixed intervals (windows) and allowing a maximum number of requests per window.

Example:

If an API allows 100 requests per minute:

  • The counter resets at the start of each minute.
  • A user making 100 requests at 00:59:59 can immediately make 100 more after 01:00:00. This can cause sudden bursts at window boundaries.

πŸ“ Example Scenario

  • Use Case: Login API with a limit of 10 attempts per minute.
  • Behavior:
    • A user can try 10 times in the current minute.
    • After the window resets, the counter refreshes, allowing another 10 attempts.

βœ… Benefits

  • Simple and easy to implement.
  • Minimal overhead and resource usage.
  • Easy to debug and understand.

⚠️ Limitations

  • Burstiness: Allows spikes at window boundaries.
  • Precision: Less smooth than sliding window methods.
  • Distributed Challenges: Single in-memory counter won’t work across multiple instances without central coordination.

πŸ’» Single-Machine Implementation (Thread-Safe Java)

When to Use:

  • Small-scale services.
  • Non-critical endpoints.
  • Internal services where distributed coordination isn’t needed.

Code for Single-Machine Fixed Window Rate Limiter:

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Single-machine Fixed Window Rate Limiter.
 * Thread-safe implementation for small-scale services.
 */
public class FixedRateLimiter implements IRateLimiter {

    private final Timer timer; // Provides current time (injectable for testing)
    private final Map<String, FixedWindow> map; // Stores request counts per user/requestId
    private final Duration windowSize; // Size of fixed time window
    private final int capacity; // Max requests per window

    public FixedRateLimiter(Duration windowSize, int capacity, Timer timer) {
        this.timer = timer;
        this.map = new ConcurrentHashMap<>();
        this.windowSize = windowSize;
        this.capacity = capacity;
    }

    /**
     * Checks if a request is allowed for the given requestId.
     *
     * @param requestId Unique identifier for a client/user
     * @return true if allowed, false if rate limit exceeded
     */
    @Override
    public boolean isAllowed(String requestId) {
        long currentTimeMillis = timer.currentTimeMillis();

        // Get or create FixedWindow for this requestId
        FixedWindow fixedWindow = map.computeIfAbsent(
            requestId,
            e -> new FixedWindow(new AtomicInteger(capacity), currentTimeMillis)
        );

        // Reset window if current time exceeds previous window
        if (currentTimeMillis - fixedWindow.lastAccessTime > windowSize.toMillis()) {
            synchronized (fixedWindow) {
                if (currentTimeMillis - fixedWindow.lastAccessTime > windowSize.toMillis()) {
                    fixedWindow.lastAccessTime = currentTimeMillis;
                    fixedWindow.requestCount.set(capacity); // Reset request count
                }
            }
        }

        // Allow request if capacity remains
        int remaining = fixedWindow.requestCount.get();
        if (remaining > 0) {
            fixedWindow.requestCount.decrementAndGet();
            return true;
        }
        return false; // Deny if limit reached
    }

    /**
     * Internal class to hold request count and last access time per window.
     */
    private static class FixedWindow {
        final AtomicInteger requestCount; // Tracks remaining requests
        volatile long lastAccessTime; // Timestamp of window start

        FixedWindow(AtomicInteger requestCount, long lastAccessTime) {
            this.requestCount = requestCount;
            this.lastAccessTime = lastAccessTime;
        }
    }

    /**
     * Timer abstraction for easy testing (injectable current time provider)
     */
    public record Timer() {
        public long currentTimeMillis() {
            return System.currentTimeMillis();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

🌐 Distributed Implementation (Redis + Java)

When to Use:

  • High-scale APIs across multiple instances.
  • Requires central coordination to prevent users from exceeding limits globally.

Code for Redis-Based Fixed Window Rate Limiter:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * Distributed Fixed Window Rate Limiter using Redis.
 * Suitable for multi-instance applications.
 */
public class RedisFixedRateLimiter {

    private final JedisPool jedisPool; // Redis connection pool
    private final String rateLimitKeyPrefix; // Prefix for Redis keys, e.g., "rate_limit:"
    private final int maxRequestsPerWindow; // Max requests allowed per window
    private final int windowDurationInSeconds; // Window size in seconds

    public RedisFixedRateLimiter(JedisPool jedisPool,
                                 String rateLimitKeyPrefix,
                                 int maxRequestsPerWindow,
                                 int windowDurationInSeconds) {
        this.jedisPool = jedisPool;
        this.rateLimitKeyPrefix = rateLimitKeyPrefix;
        this.maxRequestsPerWindow = maxRequestsPerWindow;
        this.windowDurationInSeconds = windowDurationInSeconds;
    }

    /**
     * Checks if a request is allowed for a given userId.
     *
     * @param userId Unique identifier for the client/user
     * @return true if request is allowed, false if rate limit exceeded
     */
    public boolean isRequestAllowed(String userId) {
        String key = rateLimitKeyPrefix + userId;

        try (Jedis jedis = jedisPool.getResource()) {
            // Atomically increment the request count in Redis
            long currentCount = jedis.incr(key);

            // Set expiration only for the first request in the window
            if (currentCount == 1) {
                jedis.expire(key, windowDurationInSeconds);
            }

            // Allow if count does not exceed the maximum
            return currentCount <= maxRequestsPerWindow;
        } catch (Exception e) {
            // Fail-open: allow requests if Redis is unavailable
            e.printStackTrace();
            return true;
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

⚑ Fault Tolerance for Redis

  • Redis Down: Implement a fail-open policy (allow requests) or local fallback counters.
  • Circuit Breaker: Temporarily stop excessive traffic when Redis is unreachable.
  • Graceful Degradation: Use in-memory counters with a short TTL to limit impact until Redis recovers.

βœ… Summary

  • Fixed Window is simple, efficient, and effective for many straightforward use cases.
  • Main limitation: bursts at window boundaries.
  • Single-machine approach: Suitable for low-traffic, non-critical services.
  • Redis-based approach: Ideal for distributed high-traffic environments but requires fault-tolerance planning.

Top comments (0)