Skip to main content

Overview

The RateLimitExceededException is thrown when an invocation is not allowed by the current rate limit policy. Applications typically translate this to HTTP 429 (Too Many Requests). Package: io.github.v4runsharma.ratelimiter.exception Source: RateLimitExceededException.java:15

Constructors

Constructor (without name)

public RateLimitExceededException(
    String key,
    RateLimitPolicy policy,
    RateLimitDecision decision
)
Creates an exception without a logical name.
key
String
required
The resolved bucket key that was rate limited. Must not be null or blank.
policy
RateLimitPolicy
required
The policy that was enforced. Must not be null.
decision
RateLimitDecision
required
The decision details (retryAfter/resetAfter/remaining if known). Must not be null and isAllowed() must be false.
Throws: IllegalArgumentException if:
  • key is null or blank
  • policy is null
  • decision is null
  • decision.isAllowed() is true
Source: RateLimitExceededException.java:25-27

Constructor (with name)

public RateLimitExceededException(
    String name,
    String key,
    RateLimitPolicy policy,
    RateLimitDecision decision
)
Creates an exception with an optional logical name.
name
String
Optional logical name for the limit. Can be null or blank.
key
String
required
The resolved bucket key that was rate limited. Must not be null or blank.
policy
RateLimitPolicy
required
The policy that was enforced. Must not be null.
decision
RateLimitDecision
required
The decision details. Must not be null and isAllowed() must be false.
Source: RateLimitExceededException.java:29-38

Properties

getName

public String getName()
return
String
Optional logical name for the limit (may be null).
Source: RateLimitExceededException.java:41-43

getKey

public String getKey()
return
String
The resolved bucket key that was rate limited.
Source: RateLimitExceededException.java:46-48

getPolicy

public RateLimitPolicy getPolicy()
return
RateLimitPolicy
The policy that was enforced.
Source: RateLimitExceededException.java:51-53

getDecision

public RateLimitDecision getDecision()
return
RateLimitDecision
Decision details including retryAfter, resetAfter, and remaining time if known.
Source: RateLimitExceededException.java:56-58

Usage examples

Basic exception handling

import io.github.v4runsharma.ratelimiter.exception.RateLimitExceededException;
import io.github.v4runsharma.ratelimiter.core.RateLimitEnforcer;
import io.github.v4runsharma.ratelimiter.core.RateLimitContext;

public class ServiceLayer {
    
    private final RateLimitEnforcer enforcer;
    
    public void processRequest(RateLimitContext context) {
        try {
            enforcer.enforce(context);
            
            // Process the request
            doBusinessLogic();
            
        } catch (RateLimitExceededException ex) {
            System.err.println("Rate limit exceeded: " + ex.getMessage());
            
            // Get retry information
            long retryAfterMs = ex.getDecision().getRetryAfterMillis();
            System.err.println("Retry after: " + retryAfterMs + "ms");
            
            throw ex; // Re-throw for global handler
        }
    }
    
    private void doBusinessLogic() {
        // Business logic implementation
    }
}

REST exception handler

import io.github.v4runsharma.ratelimiter.exception.RateLimitExceededException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class RateLimitExceptionHandler {
    
    @ExceptionHandler(RateLimitExceededException.class)
    public ResponseEntity<ErrorResponse> handleRateLimitExceeded(
        RateLimitExceededException ex
    ) {
        // Build error response
        ErrorResponse error = new ErrorResponse(
            "rate_limit_exceeded",
            ex.getMessage(),
            ex.getKey()
        );
        
        // Add retry headers
        HttpHeaders headers = new HttpHeaders();
        
        long retryAfterSeconds = ex.getDecision().getRetryAfterMillis() / 1000;
        if (retryAfterSeconds > 0) {
            headers.set("Retry-After", String.valueOf(retryAfterSeconds));
        }
        
        // Add custom headers
        headers.set("X-RateLimit-Limit", 
            String.valueOf(ex.getPolicy().getLimit()));
        headers.set("X-RateLimit-Window", 
            ex.getPolicy().getWindow().toString());
        
        ex.getDecision().getResetAfter().ifPresent(duration -> 
            headers.set("X-RateLimit-Reset", 
                String.valueOf(System.currentTimeMillis() + duration.toMillis()))
        );
        
        return new ResponseEntity<>(error, headers, HttpStatus.TOO_MANY_REQUESTS);
    }
    
    public record ErrorResponse(
        String code,
        String message,
        String rateLimitKey
    ) { }
}

Logging exception details

import io.github.v4runsharma.ratelimiter.exception.RateLimitExceededException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RateLimitLogger {
    
    private static final Logger log = LoggerFactory.getLogger(RateLimitLogger.class);
    
    public void logRateLimitExceeded(RateLimitExceededException ex) {
        log.warn("Rate limit exceeded - Name: {}, Key: {}, Limit: {}, Window: {}, Retry: {}ms",
            ex.getName(),
            ex.getKey(),
            ex.getPolicy().getLimit(),
            ex.getPolicy().getWindow(),
            ex.getDecision().getRetryAfterMillis()
        );
        
        // Log additional decision details
        ex.getDecision().getRetryAfter().ifPresent(duration ->
            log.debug("Retry after duration: {}", duration)
        );
        
        ex.getDecision().getResetAfter().ifPresent(duration ->
            log.debug("Reset after duration: {}", duration)
        );
    }
}

Creating the exception

import io.github.v4runsharma.ratelimiter.exception.RateLimitExceededException;
import io.github.v4runsharma.ratelimiter.model.RateLimitPolicy;
import io.github.v4runsharma.ratelimiter.model.RateLimitDecision;
import java.time.Duration;

public class EnforcerExample {
    
    public void enforceLimit() {
        // Create policy
        RateLimitPolicy policy = new RateLimitPolicy(
            100,
            Duration.ofMinutes(1),
            "user"
        );
        
        // Create decision (denied)
        RateLimitDecision decision = new RateLimitDecision(
            false,
            30000,
            Duration.ofSeconds(30),
            Duration.ofMinutes(1)
        );
        
        // Throw exception without name
        throw new RateLimitExceededException(
            "user:123",
            policy,
            decision
        );
    }
    
    public void enforceNamedLimit() {
        RateLimitPolicy policy = new RateLimitPolicy(
            1000,
            Duration.ofHours(1),
            "ip"
        );
        
        RateLimitDecision decision = new RateLimitDecision(
            false,
            60000,
            Duration.ofMinutes(1),
            Duration.ofHours(1)
        );
        
        // Throw exception with name
        throw new RateLimitExceededException(
            "public-api-limit",
            "ip:192.168.1.1",
            policy,
            decision
        );
    }
}

Metrics recording

import io.github.v4runsharma.ratelimiter.exception.RateLimitExceededException;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;

public class RateLimitMetrics {
    
    private final Counter rateLimitExceededCounter;
    
    public RateLimitMetrics(MeterRegistry registry) {
        this.rateLimitExceededCounter = Counter.builder("rate_limit.exceeded")
            .description("Number of rate limit exceeded events")
            .register(registry);
    }
    
    public void recordException(RateLimitExceededException ex) {
        rateLimitExceededCounter.increment();
        
        // Record with tags
        Counter.builder("rate_limit.exceeded")
            .tag("name", ex.getName() != null ? ex.getName() : "unknown")
            .tag("scope", ex.getPolicy().getScope())
            .register(registry)
            .increment();
    }
}

Retry mechanism

import io.github.v4runsharma.ratelimiter.exception.RateLimitExceededException;
import java.util.concurrent.TimeUnit;

public class RetryableService {
    
    private final RateLimitEnforcer enforcer;
    private final int maxRetries;
    
    public RetryableService(RateLimitEnforcer enforcer, int maxRetries) {
        this.enforcer = enforcer;
        this.maxRetries = maxRetries;
    }
    
    public void executeWithRetry(RateLimitContext context) {
        int attempt = 0;
        
        while (attempt < maxRetries) {
            try {
                enforcer.enforce(context);
                
                // Execute business logic
                processRequest();
                return;
                
            } catch (RateLimitExceededException ex) {
                attempt++;
                
                if (attempt >= maxRetries) {
                    throw ex; // Give up after max retries
                }
                
                long retryAfterMs = ex.getDecision().getRetryAfterMillis();
                
                if (retryAfterMs > 0 && 
                    retryAfterMs != RateLimitDecision.REMAINING_TIME_UNKNOWN) {
                    
                    System.out.println("Rate limited. Retrying after " 
                        + retryAfterMs + "ms (attempt " + attempt + ")");
                    
                    try {
                        TimeUnit.MILLISECONDS.sleep(retryAfterMs);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("Retry interrupted", e);
                    }
                } else {
                    throw ex; // Can't determine retry time
                }
            }
        }
    }
    
    private void processRequest() {
        // Business logic
    }
}

Message format

The exception message format is:
Rate limit exceeded: {name or key} (limit={limit}, window={window}, remaining={remainingMs})
Example messages:
Rate limit exceeded: user:123 (limit=100, window=PT1M, remaining=30000)
Rate limit exceeded: public-api-limit (limit=1000, window=PT1H, remaining=60000)
Source: RateLimitExceededException.java:60-66