Class RateLimiter

java.lang.Object
com.loomcache.server.network.RateLimiter
All Implemented Interfaces:
AutoCloseable

public class RateLimiter extends Object implements AutoCloseable
Production-quality rate limiter supporting multiple policies and per-client limiting.

Implements configurable rate limiting with:

  • Token bucket algorithm (primary implementation)
  • Per-client isolation with automatic stale entry cleanup
  • Global rate limiting across all clients
  • Multiple rate limit policies: FIXED_WINDOW, SLIDING_WINDOW, TOKEN_BUCKET
  • Comprehensive metrics tracking (total, allowed, denied, per-client stats)
  • Virtual thread compatible using ReentrantLock (not synchronized)
  • AutoCloseable for clean resource management

Token bucket algorithm:

  • Tokens accumulate at a configurable rate (requests/second)
  • Each request consumes 1 token (configurable)
  • Burst capacity allows temporary exceeding of steady-state rate
  • Full bucket never exceeds burst capacity

Per-client limiting:

  • Each client maintains independent token bucket
  • Clients identified by string ID (e.g., IP address, client UUID)
  • Stale entries auto-cleaned after a configurable inactivity threshold
  • Memory-efficient with lazy initialization

Thread safety:

  • All operations thread-safe using ReentrantLock
  • Compatible with Java 25 virtual threads
  • Atomic operations for metrics (no lock contention)

Usage example:

var limiter = new RateLimiter.Builder()
    .globalRate(1000)           // 1000 requests/sec globally
    .globalBurst(2000)          // Allow 2000 burst
    .perClientRate(100)         // 100 requests/sec per client
    .perClientBurst(200)        // Allow 200 burst per client
    .cleanupIntervalSeconds(300) // Cleanup every 5 minutes
    .build();

if (limiter.allowRequest("client-id")) {
    processRequest();
} else {
    rejectRequest("rate limited");
}
Since:
1.0
  • Method Details

    • allowRequest

      public boolean allowRequest(String clientId)
      Check if a request from a client is allowed under rate limits.

      Checks both global and per-client rate limits. Request is allowed only if both limits permit it. Automatically creates per-client bucket on first request.

      Parameters:
      clientId - unique client identifier (non-null)
      Returns:
      true if request is allowed, false if rate limited
      Throws:
      NullPointerException - if clientId is null
      IllegalStateException - if limiter is closed
    • allowRequests

      public boolean allowRequests(String clientId, int requestCount)
      Check if multiple requests are allowed (batched check).
      Parameters:
      clientId - unique client identifier (non-null)
      requestCount - number of requests to check
      Returns:
      true if all requests are allowed, false if any rate limited
    • getStats

      public RateLimiter.RateLimiterStats getStats()
      Get current rate limiter statistics snapshot.
      Returns:
      immutable statistics snapshot
    • getClientStats

      public @Nullable RateLimiter.ClientStats getClientStats(String clientId)
      Get per-client statistics.
      Parameters:
      clientId - unique client identifier (non-null)
      Returns:
      client statistics, or null if client unknown
    • reset

      public void reset()
      Reset all metrics and client buckets.
    • getActiveClientCount

      public int getActiveClientCount()
      Get the number of actively tracked clients.
      Returns:
      client count
    • getTotalAllowed

      public long getTotalAllowed()
      Get global request allowed count.
      Returns:
      total requests allowed
    • getTotalDenied

      public long getTotalDenied()
      Get global request denied count.
      Returns:
      total requests denied
    • getTotalRequests

      public long getTotalRequests()
      Get global request count.
      Returns:
      total requests
    • close

      public void close()
      Shutdown the rate limiter and clean up resources.

      Stops the background cleanup task. After close(), allowRequest() will throw.

      Specified by:
      close in interface AutoCloseable