limiters = new ConcurrentHashMap<>();
+ private final ScheduledExecutorService healthChecker = Executors.newScheduledThreadPool(1);
+
+ public AdaptiveRateLimiter(int initialLimit, int maxLimit) {
+ this.initialLimit = initialLimit;
+ this.maxLimit = maxLimit;
+ this.currentLimit = new AtomicInteger(initialLimit);
+ healthChecker.scheduleAtFixedRate(this::adjustLimits, 10, 10, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void check(String serviceName, String operationName) throws RateLimitException {
+ String key = serviceName + ":" + operationName;
+ int current = currentLimit.get();
+
+ RateLimiter limiter = limiters.computeIfAbsent(key,
+ k -> new TokenBucketRateLimiter(current, current));
+
+ try {
+ limiter.check(serviceName, operationName);
+ System.out.printf("[Adaptive] Allowed %s.%s - CurrentLimit: %d%n", serviceName, operationName, current);
+ } catch (RateLimitException e) {
+ currentLimit.updateAndGet(curr -> Math.max(initialLimit, curr / 2));
+ System.out.printf("[Adaptive] Throttled %s.%s - Decreasing limit to %d%n",
+ serviceName, operationName, currentLimit.get());
+ throw e;
+ }
+ }
+
+ private void adjustLimits() {
+ int updated = currentLimit.updateAndGet(curr -> Math.min(maxLimit, curr + (initialLimit / 2)));
+ System.out.printf("[Adaptive] Health check passed - Increasing limit to %d%n", updated);
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/App.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/App.java
new file mode 100644
index 000000000000..1774a9301cec
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/App.java
@@ -0,0 +1,164 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import java.util.Random;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * The Rate Limiter pattern is a key defensive strategy used to prevent system overload
+ * and ensure fair usage of shared services. This demo showcases how different rate limiting techniques
+ * can regulate traffic in distributed systems.
+ *
+ * Specifically, this simulation implements three rate limiter strategies:
+ *
+ *
+ * - Token Bucket – Allows short bursts followed by steady request rates.
+ * - Fixed Window – Enforces a strict limit per discrete time window (e.g., 3 requests/sec).
+ * - Adaptive – Dynamically scales limits based on system health, simulating elastic backoff.
+ *
+ *
+ * Each simulated service (e.g., S3, DynamoDB, Lambda) is governed by one of these limiters. Multiple
+ * concurrent client threads issue randomized requests to these services over a fixed duration. Each
+ * request is either:
+ *
+ *
+ * - ALLOWED – Permitted under the current rate limit
+ * - THROTTLED – Rejected due to quota exhaustion
+ * - FAILED – Dropped due to transient service failure
+ *
+ *
+ * Statistics are printed every few seconds, and the simulation exits gracefully after a fixed runtime,
+ * offering a clear view into how each limiter behaves under pressure.
+ *
+ *
Relation to AWS API Gateway:
+ * This implementation mirrors the throttling behavior described in the
+ *
+ * AWS API Gateway Request Throttling documentation, where limits are applied per second and over
+ * longer durations (burst and rate limits). The TokenBucketRateLimiter
mimics burst capacity,
+ * the FixedWindowRateLimiter
models steady rate enforcement, and the AdaptiveRateLimiter
+ * reflects elasticity in real-world systems like AWS Lambda under variable load.
+ *
+ * */
+public final class App {
+ private static final int RUN_DURATION_SECONDS = 10;
+ private static final int SHUTDOWN_TIMEOUT_SECONDS = 5;
+
+ private static final AtomicInteger successfulRequests = new AtomicInteger();
+ private static final AtomicInteger throttledRequests = new AtomicInteger();
+ private static final AtomicInteger failedRequests = new AtomicInteger();
+ private static final AtomicBoolean running = new AtomicBoolean(true);
+
+ public static void main(String[] args) {
+ System.out.println("\nStarting Rate Limiter Demo");
+ System.out.println("====================================");
+
+ ExecutorService executor = Executors.newFixedThreadPool(3);
+ ScheduledExecutorService statsPrinter = Executors.newSingleThreadScheduledExecutor();
+
+ try {
+ // Explicit limiter setup for demonstration clarity
+ TokenBucketRateLimiter tb = new TokenBucketRateLimiter(2, 1); // capacity 2, refill 1/sec
+ FixedWindowRateLimiter fw = new FixedWindowRateLimiter(3, 1); // max 3 req/sec
+ AdaptiveRateLimiter ar = new AdaptiveRateLimiter(2, 6); // adaptive from 2 to 6 req/sec
+
+ // Print statistics every 2 seconds
+ statsPrinter.scheduleAtFixedRate(App::printStats, 2, 2, TimeUnit.SECONDS);
+
+ // Launch 3 simulated clients
+ for (int i = 1; i <= 3; i++) {
+ executor.submit(createClientTask(i, tb, fw, ar));
+ }
+
+ // Run simulation for N seconds
+ Thread.sleep(RUN_DURATION_SECONDS * 1000L);
+ System.out.println("\nShutting down the demo...");
+
+ running.set(false);
+ executor.shutdown();
+ statsPrinter.shutdown();
+
+ if (!executor.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
+ executor.shutdownNow();
+ }
+ if (!statsPrinter.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
+ statsPrinter.shutdownNow();
+ }
+
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } finally {
+ printFinalStats();
+ System.out.println("Demo completed.");
+ }
+ }
+
+ private static Runnable createClientTask(int clientId,
+ RateLimiter s3Limiter,
+ RateLimiter dynamoDbLimiter,
+ RateLimiter lambdaLimiter) {
+ return () -> {
+ String[] services = {"s3", "dynamodb", "lambda"};
+ String[] operations = {
+ "GetObject", "PutObject", "Query", "Scan", "PutItem", "Invoke", "ListFunctions"
+ };
+ Random random = new Random();
+
+ while (running.get() && !Thread.currentThread().isInterrupted()) {
+ try {
+ String service = services[random.nextInt(services.length)];
+ String operation = operations[random.nextInt(operations.length)];
+
+ switch (service) {
+ case "s3" -> makeRequest(clientId, s3Limiter, service, operation);
+ case "dynamodb" -> makeRequest(clientId, dynamoDbLimiter, service, operation);
+ case "lambda" -> makeRequest(clientId, lambdaLimiter, service, operation);
+ }
+
+ Thread.sleep(30 + random.nextInt(50));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ };
+ }
+
+ private static void makeRequest(int clientId, RateLimiter limiter,
+ String service, String operation) {
+ try {
+ limiter.check(service, operation);
+ successfulRequests.incrementAndGet();
+ System.out.printf("Client %d: %s.%s - ALLOWED%n", clientId, service, operation);
+ } catch (ThrottlingException e) {
+ throttledRequests.incrementAndGet();
+ System.out.printf("Client %d: %s.%s - THROTTLED (Retry in %dms)%n",
+ clientId, service, operation, e.getRetryAfterMillis());
+ } catch (ServiceUnavailableException e) {
+ failedRequests.incrementAndGet();
+ System.out.printf("Client %d: %s.%s - SERVICE UNAVAILABLE%n",
+ clientId, service, operation);
+ } catch (Exception e) {
+ failedRequests.incrementAndGet();
+ System.out.printf("Client %d: %s.%s - ERROR: %s%n",
+ clientId, service, operation, e.getMessage());
+ }
+ }
+
+ private static void printStats() {
+ if (!running.get()) return;
+ System.out.println("\n=== Current Statistics ===");
+ System.out.printf("Successful Requests: %d%n", successfulRequests.get());
+ System.out.printf("Throttled Requests : %d%n", throttledRequests.get());
+ System.out.printf("Failed Requests : %d%n", failedRequests.get());
+ System.out.println("==========================\n");
+ }
+
+ private static void printFinalStats() {
+ System.out.println("\nFinal Statistics");
+ System.out.println("==========================");
+ System.out.printf("Successful Requests: %d%n", successfulRequests.get());
+ System.out.printf("Throttled Requests : %d%n", throttledRequests.get());
+ System.out.printf("Failed Requests : %d%n", failedRequests.get());
+ System.out.println("==========================");
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequest.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequest.java
new file mode 100644
index 000000000000..28ada548a1d0
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequest.java
@@ -0,0 +1,38 @@
+package com.iluwatar.rate.limiting.pattern;
+
+/**
+ * Example operation implementation for finding customers.
+ */
+public class FindCustomerRequest implements RateLimitOperation {
+ private final String customerId;
+ private final RateLimiter rateLimiter;
+
+ public FindCustomerRequest(String customerId, RateLimiter rateLimiter) {
+ this.customerId = customerId;
+ this.rateLimiter = rateLimiter;
+ }
+
+ @Override
+ public String getServiceName() {
+ return "CustomerService";
+ }
+
+ @Override
+ public String getOperationName() {
+ return "FindCustomer";
+ }
+
+ @Override
+ public String execute() throws RateLimitException {
+ rateLimiter.check(getServiceName(), getOperationName());
+
+ // Simulate actual operation
+ try {
+ Thread.sleep(50); // Simulate processing time
+ return "Customer-" + customerId;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new ServiceUnavailableException(getServiceName(), 1000);
+ }
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiter.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiter.java
new file mode 100644
index 000000000000..697cecd5d7e0
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiter.java
@@ -0,0 +1,46 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Fixed window rate limiter implementation.
+ */
+public class FixedWindowRateLimiter implements RateLimiter {
+ private final int limit;
+ private final long windowMillis;
+ private final ConcurrentHashMap counters = new ConcurrentHashMap<>();
+
+ public FixedWindowRateLimiter(int limit, long windowSeconds) {
+ this.limit = limit;
+ this.windowMillis = TimeUnit.SECONDS.toMillis(windowSeconds);
+ }
+
+ @Override
+ public synchronized void check(String serviceName, String operationName) throws RateLimitException {
+ String key = serviceName + ":" + operationName;
+ WindowCounter counter = counters.computeIfAbsent(key, k -> new WindowCounter());
+
+ if (!counter.tryIncrement()) {
+ System.out.printf("[FixedWindow] Throttled %s.%s - Limit %d reached in window%n",
+ serviceName, operationName, limit);
+ throw new RateLimitException("Rate limit exceeded for " + key, windowMillis);
+ } else {
+ System.out.printf("[FixedWindow] Allowed %s.%s - Count within window%n", serviceName, operationName);
+ }
+ }
+
+ private class WindowCounter {
+ private AtomicInteger count = new AtomicInteger(0);
+ private volatile long windowStart = System.currentTimeMillis();
+
+ synchronized boolean tryIncrement() {
+ long now = System.currentTimeMillis();
+ if (now - windowStart > windowMillis) {
+ count.set(0);
+ windowStart = now;
+ }
+ return count.incrementAndGet() <= limit;
+ }
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitException.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitException.java
new file mode 100644
index 000000000000..788b9fcd816c
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitException.java
@@ -0,0 +1,17 @@
+package com.iluwatar.rate.limiting.pattern;
+
+/**
+ * Base exception for rate limiting errors.
+ */
+public class RateLimitException extends Exception {
+ private final long retryAfterMillis;
+
+ public RateLimitException(String message, long retryAfterMillis) {
+ super(message);
+ this.retryAfterMillis = retryAfterMillis;
+ }
+
+ public long getRetryAfterMillis() {
+ return retryAfterMillis;
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitOperation.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitOperation.java
new file mode 100644
index 000000000000..243ea66106dc
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimitOperation.java
@@ -0,0 +1,10 @@
+package com.iluwatar.rate.limiting.pattern;
+
+/**
+ * Interface representing an operation that needs rate limiting.
+ */
+public interface RateLimitOperation {
+ String getServiceName();
+ String getOperationName();
+ T execute() throws RateLimitException;
+}
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimiter.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimiter.java
new file mode 100644
index 000000000000..3c6b15e1a47f
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/RateLimiter.java
@@ -0,0 +1,14 @@
+package com.iluwatar.rate.limiting.pattern;
+
+/**
+ * Interface for rate limiting operations.
+ */
+public interface RateLimiter {
+ /**
+ * Checks if a request is allowed under current rate limits
+ * @param serviceName Service being called (e.g., "dynamodb")
+ * @param operationName Operation being performed (e.g., "Query")
+ * @throws RateLimitException if request exceeds limits
+ */
+ void check(String serviceName, String operationName) throws RateLimitException;
+}
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ServiceUnavailableException.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ServiceUnavailableException.java
new file mode 100644
index 000000000000..04426330a13a
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ServiceUnavailableException.java
@@ -0,0 +1,17 @@
+package com.iluwatar.rate.limiting.pattern;
+
+/**
+ * Exception for when a service is temporarily unavailable.
+ */
+public class ServiceUnavailableException extends RateLimitException {
+ private final String serviceName;
+
+ public ServiceUnavailableException(String serviceName, long retryAfterMillis) {
+ super("Service temporarily unavailable: " + serviceName, retryAfterMillis);
+ this.serviceName = serviceName;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+}
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ThrottlingException.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ThrottlingException.java
new file mode 100644
index 000000000000..2c85621640e5
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/ThrottlingException.java
@@ -0,0 +1,23 @@
+package com.iluwatar.rate.limiting.pattern;
+
+/**
+ * Exception thrown when AWS-style throttling occurs.
+ */
+public class ThrottlingException extends RateLimitException {
+ private final String serviceName;
+ private final String errorCode;
+
+ public ThrottlingException(String serviceName, String operationName, long retryAfterMillis) {
+ super("AWS throttling error for " + serviceName + "/" + operationName, retryAfterMillis);
+ this.serviceName = serviceName;
+ this.errorCode = "ThrottlingException";
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public String getErrorCode() {
+ return errorCode;
+ }
+}
diff --git a/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiter.java b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiter.java
new file mode 100644
index 000000000000..549dfae5c720
--- /dev/null
+++ b/rate-limiting-pattern/src/main/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiter.java
@@ -0,0 +1,60 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Token bucket rate limiter implementation.
+ */
+public class TokenBucketRateLimiter implements RateLimiter {
+ private final int capacity;
+ private final int refillRate;
+ private final ConcurrentHashMap buckets = new ConcurrentHashMap<>();
+ private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+
+ public TokenBucketRateLimiter(int capacity, int refillRate) {
+ this.capacity = capacity;
+ this.refillRate = refillRate;
+ scheduler.scheduleAtFixedRate(this::refillBuckets, 1, 1, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void check(String serviceName, String operationName) throws RateLimitException {
+ String key = serviceName + ":" + operationName;
+ TokenBucket bucket = buckets.computeIfAbsent(key, k -> new TokenBucket(capacity));
+
+ if (!bucket.tryConsume()) {
+ System.out.printf("[TokenBucket] Throttled %s.%s - No tokens available%n", serviceName, operationName);
+ throw new ThrottlingException(serviceName, operationName, 1000);
+ } else {
+ System.out.printf("[TokenBucket] Allowed %s.%s - Tokens remaining%n", serviceName, operationName);
+ }
+ }
+
+
+ private void refillBuckets() {
+ buckets.forEach((k, b) -> b.refill(refillRate));
+ }
+
+ private static class TokenBucket {
+ private final int capacity;
+ private final AtomicInteger tokens;
+
+ TokenBucket(int capacity) {
+ this.capacity = capacity;
+ this.tokens = new AtomicInteger(capacity);
+ }
+
+ boolean tryConsume() {
+ while (true) {
+ int current = tokens.get();
+ if (current <= 0) return false;
+ if (tokens.compareAndSet(current, current - 1)) return true;
+ }
+ }
+
+ void refill(int amount) {
+ tokens.getAndUpdate(current -> Math.min(current + amount, capacity));
+ }
+ }
+}
diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiterTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiterTest.java
new file mode 100644
index 000000000000..b3207c019390
--- /dev/null
+++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/AdaptiveRateLimiterTest.java
@@ -0,0 +1,48 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class AdaptiveRateLimiterTest {
+ @Test
+ void shouldDecreaseLimitWhenThrottled() throws Exception {
+ AdaptiveRateLimiter limiter = new AdaptiveRateLimiter(10, 20);
+
+ // Exceed initial limit
+ for (int i = 0; i < 11; i++) {
+ try {
+ limiter.check("test", "op");
+ } catch (RateLimitException e) {
+ // Expected after 10 requests
+ }
+ }
+
+ // Verify limit was reduced
+ assertThrows(RateLimitException.class, () -> {
+ for (int i = 0; i < 6; i++) { // New limit should be 5 (10/2)
+ limiter.check("test", "op");
+ }
+ });
+ }
+
+ @Test
+ void shouldGraduallyIncreaseLimitWhenHealthy() throws Exception {
+ AdaptiveRateLimiter limiter = new AdaptiveRateLimiter(10, 20);
+
+ // Force limit reduction
+ for (int i = 0; i < 11; i++) {
+ try {
+ limiter.check("test", "op");
+ } catch (RateLimitException e) {}
+ }
+
+ // Wait for health check (adjustment happens every 10 seconds)
+ Thread.sleep(10500);
+
+ // Verify limit was increased
+ for (int i = 0; i < 8; i++) { // New limit should be 10 (5 + 5)
+ limiter.check("test", "op");
+ }
+ assertThrows(RateLimitException.class, () -> limiter.check("test", "op"));
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ConcurrencyTests.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ConcurrencyTests.java
new file mode 100644
index 000000000000..f4efb723d29e
--- /dev/null
+++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ConcurrencyTests.java
@@ -0,0 +1,65 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import org.junit.jupiter.api.Test;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ConcurrencyTests {
+ @Test
+ void tokenBucketShouldHandleConcurrentRequests() throws Exception {
+ int threadCount = 10;
+ int requestLimit = 5;
+ RateLimiter limiter = new TokenBucketRateLimiter(requestLimit, requestLimit);
+ ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+ CountDownLatch latch = new CountDownLatch(threadCount);
+
+ AtomicInteger successCount = new AtomicInteger();
+ AtomicInteger failureCount = new AtomicInteger();
+
+ for (int i = 0; i < threadCount; i++) {
+ executor.submit(() -> {
+ try {
+ limiter.check("test", "op");
+ successCount.incrementAndGet();
+ } catch (RateLimitException e) {
+ failureCount.incrementAndGet();
+ }
+ latch.countDown();
+ });
+ }
+
+ latch.await();
+ assertEquals(requestLimit, successCount.get());
+ assertEquals(threadCount - requestLimit, failureCount.get());
+ }
+
+ @Test
+ void adaptiveLimiterShouldAdjustUnderLoad() throws Exception {
+ AdaptiveRateLimiter limiter = new AdaptiveRateLimiter(10, 20);
+ ExecutorService executor = Executors.newFixedThreadPool(20);
+
+ // Flood with requests to trigger throttling
+ for (int i = 0; i < 30; i++) {
+ executor.submit(() -> {
+ try {
+ limiter.check("test", "op");
+ } catch (RateLimitException ignored) {}
+ });
+ }
+
+ Thread.sleep(15000); // Wait for adjustment
+
+ // Verify new limit is in effect
+ int allowed = 0;
+ for (int i = 0; i < 20; i++) {
+ try {
+ limiter.check("test", "op");
+ allowed++;
+ } catch (RateLimitException ignored) {}
+ }
+
+ assertTrue(allowed > 5 && allowed < 15); // Should be between initial and max
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ExceptionTests.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ExceptionTests.java
new file mode 100644
index 000000000000..6cbeee082100
--- /dev/null
+++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/ExceptionTests.java
@@ -0,0 +1,27 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class ExceptionTests {
+ @Test
+ void rateLimitExceptionShouldContainRetryInfo() {
+ RateLimitException exception = new RateLimitException("Test", 1000);
+ assertEquals(1000, exception.getRetryAfterMillis());
+ assertEquals("Test", exception.getMessage());
+ }
+
+ @Test
+ void throttlingExceptionShouldContainServiceInfo() {
+ ThrottlingException exception = new ThrottlingException("dynamodb", "Query", 500);
+ assertEquals("dynamodb", exception.getServiceName());
+ assertEquals("ThrottlingException", exception.getErrorCode());
+ }
+
+ @Test
+ void serviceUnavailableExceptionShouldContainRetryInfo() {
+ ServiceUnavailableException exception = new ServiceUnavailableException("s3", 2000);
+ assertEquals("s3", exception.getServiceName());
+ assertEquals(2000, exception.getRetryAfterMillis());
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequestTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequestTest.java
new file mode 100644
index 000000000000..4e075fd4cfd7
--- /dev/null
+++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FindCustomerRequestTest.java
@@ -0,0 +1,45 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class FindCustomerRequestTest implements RateLimitOperationTest {
+
+ @Override
+ public RateLimitOperation createOperation(RateLimiter limiter) {
+ return new FindCustomerRequest("123", limiter);
+ }
+
+ @Test
+ void shouldExecuteWhenUnderRateLimit() throws Exception {
+ RateLimiter limiter = new TokenBucketRateLimiter(10, 10);
+ RateLimitOperation request = createOperation(limiter);
+
+ String result = request.execute();
+ assertEquals("Customer-123", result);
+ }
+
+ @Test
+ void shouldThrowWhenRateLimitExceeded() {
+ RateLimiter limiter = new TokenBucketRateLimiter(0, 0); // Always throttled
+ RateLimitOperation request = createOperation(limiter);
+
+ assertThrows(RateLimitException.class, request::execute);
+ }
+
+ @Test
+ void shouldReturnCorrectServiceAndOperationNames() {
+ RateLimiter limiter = new TokenBucketRateLimiter(10, 10);
+ FindCustomerRequest request = new FindCustomerRequest("123", limiter);
+
+ assertEquals("CustomerService", request.getServiceName());
+ assertEquals("FindCustomer", request.getOperationName());
+ }
+
+ // Reuse helper logic from the interface for coverage
+ @Test
+ void shouldExecuteUsingDefaultHelper() throws Exception {
+ RateLimiter limiter = new TokenBucketRateLimiter(5, 5);
+ shouldExecuteWhenUnderLimit(createOperation(limiter));
+ }
+}
diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiterTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiterTest.java
new file mode 100644
index 000000000000..71b798cd9388
--- /dev/null
+++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/FixedWindowRateLimiterTest.java
@@ -0,0 +1,31 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import org.junit.jupiter.api.Test;
+import java.util.concurrent.TimeUnit;
+import static org.junit.jupiter.api.Assertions.*;
+
+class FixedWindowRateLimiterTest extends RateLimiterTest {
+ @Override
+ protected RateLimiter createRateLimiter(int limit, long windowMillis) {
+ return new FixedWindowRateLimiter(limit, windowMillis / 1000);
+ }
+
+ @Test
+ void shouldResetCounterAfterWindow() throws Exception {
+ FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(1, 1);
+ limiter.check("test", "op");
+ assertThrows(RateLimitException.class, () -> limiter.check("test", "op"));
+
+ TimeUnit.SECONDS.sleep(1);
+ limiter.check("test", "op");
+ }
+
+ @Test
+ void shouldNotAllowMoreThanLimitInWindow() throws Exception {
+ FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(3, 1);
+ for (int i = 0; i < 3; i++) {
+ limiter.check("test", "op");
+ }
+ assertThrows(RateLimitException.class, () -> limiter.check("test", "op"));
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimitOperationTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimitOperationTest.java
new file mode 100644
index 000000000000..7a00a584d3d1
--- /dev/null
+++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimitOperationTest.java
@@ -0,0 +1,21 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+interface RateLimitOperationTest {
+
+ RateLimitOperation createOperation(RateLimiter limiter);
+
+ @Test
+ default void shouldThrowWhenRateLimited() {
+ RateLimiter limiter = new TokenBucketRateLimiter(0, 0); // Always throttled
+ RateLimitOperation operation = createOperation(limiter);
+ assertThrows(RateLimitException.class, operation::execute);
+ }
+
+ // ✅ No @Test here, just a helper method
+ default void shouldExecuteWhenUnderLimit(RateLimitOperation operation) throws Exception {
+ assertNotNull(operation.execute());
+ }
+}
diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimiterTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimiterTest.java
new file mode 100644
index 000000000000..92ec44457dfe
--- /dev/null
+++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/RateLimiterTest.java
@@ -0,0 +1,25 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+public abstract class RateLimiterTest {
+ protected abstract RateLimiter createRateLimiter(int limit, long windowMillis);
+
+ @Test
+ void shouldAllowRequestsWithinLimit() throws Exception {
+ RateLimiter limiter = createRateLimiter(5, 1000);
+ for (int i = 0; i < 5; i++) {
+ limiter.check("test", "op");
+ }
+ }
+
+ @Test
+ void shouldThrowWhenLimitExceeded() throws Exception {
+ RateLimiter limiter = createRateLimiter(2, 1000);
+ limiter.check("test", "op");
+ limiter.check("test", "op");
+ assertThrows(RateLimitException.class, () -> limiter.check("test", "op"));
+ }
+}
\ No newline at end of file
diff --git a/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiterTest.java b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiterTest.java
new file mode 100644
index 000000000000..ea8bc530d058
--- /dev/null
+++ b/rate-limiting-pattern/src/test/java/com/iluwatar/rate/limiting/pattern/TokenBucketRateLimiterTest.java
@@ -0,0 +1,38 @@
+package com.iluwatar.rate.limiting.pattern;
+
+import org.junit.jupiter.api.Test;
+import java.util.concurrent.TimeUnit;
+import static org.junit.jupiter.api.Assertions.*;
+
+class TokenBucketRateLimiterTest extends RateLimiterTest {
+ @Override
+ protected RateLimiter createRateLimiter(int limit, long windowMillis) {
+ return new TokenBucketRateLimiter(limit, (int)(limit * 1000 / windowMillis));
+ }
+
+ @Test
+ void shouldAllowBurstRequests() throws Exception {
+ TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(10, 5);
+ for (int i = 0; i < 10; i++) {
+ limiter.check("test", "op");
+ }
+ }
+
+ @Test
+ void shouldRefillTokensAfterTime() throws Exception {
+ TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(1, 1);
+ limiter.check("test", "op");
+ assertThrows(RateLimitException.class, () -> limiter.check("test", "op"));
+
+ TimeUnit.SECONDS.sleep(1);
+ limiter.check("test", "op");
+ }
+
+ @Test
+ void shouldHandleMultipleServicesSeparately() throws Exception {
+ TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(1, 1);
+ limiter.check("service1", "op");
+ limiter.check("service2", "op");
+ assertThrows(RateLimitException.class, () -> limiter.check("service1", "op"));
+ }
+}
\ No newline at end of file
From ed01b7be4dc1a31705fad29097ce11a0791c1b04 Mon Sep 17 00:00:00 2001
From: Soham Kamble <121136639+skamble2@users.noreply.github.com>
Date: Sun, 1 Jun 2025 08:42:23 -0500
Subject: [PATCH 02/18] Added Class Diagram and Flow Diagrams for Adaptive,
Fixed Window and Token Bucket Rate Limiter
---
.../etc/Adaptive Rate Limiter.png | Bin 0 -> 117129 bytes
.../etc/Fixed Window Rate Limiter.png | Bin 0 -> 105244 bytes
.../etc/Token Bucket Rate Limiter.png | Bin 0 -> 122458 bytes
.../etc/UML Class Diagram.png | Bin 0 -> 122880 bytes
4 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 rate-limiting-pattern/etc/Adaptive Rate Limiter.png
create mode 100644 rate-limiting-pattern/etc/Fixed Window Rate Limiter.png
create mode 100644 rate-limiting-pattern/etc/Token Bucket Rate Limiter.png
create mode 100644 rate-limiting-pattern/etc/UML Class Diagram.png
diff --git a/rate-limiting-pattern/etc/Adaptive Rate Limiter.png b/rate-limiting-pattern/etc/Adaptive Rate Limiter.png
new file mode 100644
index 0000000000000000000000000000000000000000..2d849ee8d05757acbbf37b980d7579fe6872364e
GIT binary patch
literal 117129
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