Backend Development 9 min read

Implementing Custom Rate Limiting in Java with ReentrantLock and AtomicInteger

This article explains the purpose and benefits of rate limiting, reviews popular Java rate‑limiting libraries, and provides a step‑by‑step guide with complete source code for building a simple, thread‑safe custom rate limiter using maps, ReentrantLock, and AtomicInteger.

FunTester
FunTester
FunTester
Implementing Custom Rate Limiting in Java with ReentrantLock and AtomicInteger

Rate limiting is a technique used in service programs to restrict the request rate of a particular class of requests, preventing excessive consumption of server resources and ensuring service stability.

The main benefits of rate limiting include protecting system stability, preventing abuse and malicious attacks such as DoS, controlling resource consumption, and improving overall service quality.

Protect system stability: Limits prevent overload and performance degradation.

Prevent abuse and attacks: Reduces risk of DoS, brute‑force, and other malicious activities.

Control resource consumption: Ensures limited resources like databases are not over‑queried.

Improve service quality: Decreases queueing time and provides fairer resource distribution.

Common Java frameworks for rate limiting include Guava RateLimiter, Resilience4j, Alibaba Sentinel, Netflix Hystrix, and Bucket4j. While powerful, these frameworks may be overkill for simple scenarios where only basic request throttling is needed.

Approach

The custom implementation uses a Map<String, LimitConfig> to store per‑key configurations (maximum count and time window). Three additional Map<String, Integer> structures track the last request timestamp, request count, and a lock object for each key. Thread safety is ensured with a global ReentrantLock for configuration changes and per‑key ReentrantLock instances for request handling, while request counts are stored in java.util.concurrent.atomic.AtomicInteger objects.

Code

Below is the complete source code of the custom rate limiter.

import com.funtester.frame.SourceCode
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock

/**
 * Rate limiting utility supporting N/M throttling
 */
class RateLimit {
    /** total rate‑limit configuration */
    Map
config = [:]
    /** last request timestamp */
    Map
lastTime = [:]
    /** request count */
    Map
requestTimes = [:]
    /** lock for each key */
    Map
allLock = [:]
    /** write lock for configuration updates */
    ReentrantLock writeLock = new ReentrantLock()

    /**
     * Determines whether the given key is currently limited.
     */
    boolean isLimit(String key) {
        if (!config.containsKey(key)) {
            addConfig(key, 2, 2) // default configuration
            return isLimit(key) // recursive initialization
        }
        def mark = SourceCode.getMark()
        if (mark - lastTime[key] >= config[key].duration) { // new time window
            if (allLock[key].tryLock(1, TimeUnit.SECONDS)) {
                if (mark - lastTime[key] >= config[key].duration) {
                    lastTime[key] = mark
                    requestTimes[key] = new AtomicInteger(1)
                    allLock[key].unlock()
                    return false
                } else {
                    return true
                }
            }
        }
        if (requestTimes[key].get() >= config[key].maxTimes) {
            return true // exceeded max count
        }
        requestTimes[key].getAndIncrement()
        return false
    }

    /**
     * Adds a rate‑limit configuration for a specific key.
     */
    def addConfig(String key, int maxTimes, int duration) {
        if (writeLock.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (!config.containsKey(key)) {
                    config[key] = new LimitConfig(maxTimes: maxTimes, duration: duration)
                    allLock[key] = new ReentrantLock()
                    lastTime[key] = SourceCode.getMark()
                    requestTimes[key] = new AtomicInteger(0)
                }
            } finally {
                writeLock.unlock()
            }
        }
    }

    /**
     * Rate‑limit configuration class.
     */
    static class LimitConfig {
        /** maximum allowed requests */
        int maxTimes
        /** time window in seconds */
        int duration
    }
}

Test

The following script demonstrates how to use the custom limiter.

import com.funtester.httpclient.FunHttp
import com.funtester.utils.RateLimit

class Routine extends FunHttp {
    static void main(String[] args) {
        def limit = new RateLimit()
        limit.addConfig("test", 1, 1)
        1000.times {
            sleep(0.1)
            fun {
                def limit1 = limit.isLimit("t4est")
                if (!limit1) {
                    output("未限流")
                }
            }
        }
    }
}

Console output shows that the default 2 requests per 2 seconds configuration takes effect.

2021 Original Collection 2022 Original Collection 2023 Original Collection API Function Test Series Performance Test Series Java, Groovy, Go, Python
BackendJavaPerformanceConcurrencyRate Limitingtoken bucket
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.