Class RateLimiter

java.lang.Object
org.omnifaces.cdi.ratelimit.RateLimiter

@ApplicationScoped public class RateLimiter extends Object

CDI managed bean for rate limiting requests per client identifier. This utility provides a thread-safe sliding window rate limiting mechanism with configurable request limits and time windows.

The rate limiter supports both HTTP request-based rate limiting (using client IP addresses) and custom client identifier-based rate limiting (using any string identifier such as user IDs, API keys, etc.). It uses a ConcurrentHashMap to track request counts per client identifier and automatically cleans up expired entries to prevent memory leaks.

When the rate limit is exceeded, the rate limiter will by default immediately throw a RateLimitExceededException. Optionally, you can configure automatic retries via the maxRetries parameter, which will retry the request after a calculated delay based on the remaining time window. If all retries are exhausted, a RateLimitExceededException is thrown.

Usage

The recommended usage is with the RateLimit annotation.

 @Named
 @RequestScoped
 public class ApiController {

     @RateLimit(clientId = "FooAPI", maxRequestsPerTimeWindow = 10, maxRetries = 3)
     public void processFooApiRequest() {
         // Process Foo API request ...
     }
 }
 

For more fine grained usage, or when you have a variable rate limit parameter which therefore cannot be set as an annotation attribute, then you can inject this CDI bean in any CDI managed artifact and explicitly invoke either checkRateLimit(HttpServletRequest, int, Duration, int) or checkRateLimit(String, int, Duration, int). Below is an example of a servlet filter that applies rate limiting to all requests depending on client IP address:

 @WebFilter("/*")
 public class RateLimitFilter extends HttpFilter {

     @Inject
     private RateLimiter rateLimiter;

     @Override
     public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
         var maxRequestsPerTimeWindow = determineMaxRequestsBasedOn(request);
         var timeWindowInSeconds = determineTimeWindowBasedOn(request);
         var maxRetries = 0;

         try {
             rateLimiter.checkRateLimit(request, maxRequestsPerTimeWindow, Duration.ofSeconds(timeWindowInSeconds), maxRetries);
             chain.doFilter(request, response);
         }
         catch (RateLimitExceededException e) {
             response.sendError(429); // Too Many Requests
         }
     }
 }
 
Since:
5.0
Author:
Bauke Scholtz
See Also:
  • Constructor Details

    • RateLimiter

      public RateLimiter()
  • Method Details

    • init

      @PostConstruct public void init()
      First looks up the JEE default managed scheduled executor service in JNDI. If it isn't available, then create scheduled executor service ourselves with help of Executors.newSingleThreadExecutor().
    • checkRateLimit

      public void checkRateLimit(HttpServletRequest request, int maxRequestsPerTimeWindow, Duration timeWindow, int maxRetries) throws RateLimitExceededException
      Checks if the current request exceeds the configured rate limit for the client IP address associated with the given request.

      This method implements a sliding window rate limiting algorithm. It tracks the number of requests per IP address within the specified time window and throws an exception if the limit is exceeded. Expired tracking entries are automatically cleaned up to prevent memory leaks.

      Parameters:
      request - The HTTP servlet request to check.
      maxRequestsPerTimeWindow - The maximum number of requests allowed within the time window.
      timeWindow - The time window duration.
      maxRetries - The maximum number of retries.
      Throws:
      RateLimitExceededException - When the rate limit is exceeded for the client IP address associated with the given request.
    • checkRateLimit

      public void checkRateLimit(String clientId, int maxRequestsPerTimeWindow, Duration timeWindow, int maxRetries) throws RateLimitExceededException
      Checks if the current request exceeds the configured rate limit for the given client identifier.

      This method implements a sliding window rate limiting algorithm. It tracks the number of requests per client identifier within the specified time window and throws an exception if the limit is exceeded. Expired tracking entries are automatically cleaned up to prevent memory leaks.

      Parameters:
      clientId - The client identifier to check, whether client IP, user ID, API key, etc.
      maxRequestsPerTimeWindow - The maximum number of requests allowed within the time window.
      timeWindow - The time window duration.
      maxRetries - The maximum number of retries.
      Throws:
      RateLimitExceededException - When the rate limit is exceeded for the given client identifier.
    • destroy

      @PreDestroy public void destroy()
      If the scheduled executor service was created with help of Executors.newSingleThreadExecutor(), then attempt to orderly shut down it. If it's still not shut down after 5 seconds, then terminate it.