How to Ensure API Idempotency and Implement Distributed Rate Limiting in Java

This guide explains the principles of API idempotency using unique business IDs or token mechanisms, explores distributed rate‑limiting dimensions, compares token‑bucket and leaky‑bucket algorithms, and provides concrete implementations with Guava RateLimiter, Nginx configuration, and a Redis‑Lua script integrated into Spring Boot, including annotation‑based AOP for easy usage.

IT Architects Alliance
IT Architects Alliance
IT Architects Alliance
How to Ensure API Idempotency and Implement Distributed Rate Limiting in Java

API Idempotency

Idempotency ensures that multiple identical requests produce the same result without side effects (e.g., duplicate payments). The typical approach is to use a unique business identifier or a token together with version checking or distributed locks.

Update operation idempotency

Store a version number in a hidden field and include it in the UPDATE statement as a condition:

UPDATE table_name
SET version = version + 1, column = #{value}
WHERE id = #{id} AND version = #{version};

Token‑based idempotency for update/insert

Generate a token on the registration page, return it in a hidden field, and use the token to acquire a distributed lock before performing the INSERT/UPDATE. The lock is held until it expires, preventing duplicate execution.

Distributed Rate Limiting

Rate limiting can be applied along several dimensions:

Time window (QPS, requests per minute, etc.)

Resource limits (max connections, bandwidth)

Black/white lists

Distributed environment (rules shared across a cluster)

Multiple rules can be combined, e.g., limit each IP to 10 RPS and each server to 1000 QPS.

Common algorithms

Token Bucket

A bucket with a fixed capacity receives tokens at a constant rate. A request proceeds only if a token is available; excess tokens are discarded when the bucket is full.

Leaky Bucket

Incoming requests are queued in a bucket and released at a constant rate, smoothing bursts and preventing sudden spikes.

Implementation options

Guava RateLimiter (client‑side)

Add Guava 18.0 to Maven and create a RateLimiter. Example controller:

@RestController
@Slf4j
public class RateController {
    private final RateLimiter limiter = RateLimiter.create(2.0); // 2 tokens per second

    @GetMapping("/tryAcquire")
    public String tryAcquire(Integer count) {
        if (limiter.tryAcquire(count)) {
            log.info("Success, rate {}", limiter.getRate());
            return "success";
        }
        log.info("Rejected, rate {}", limiter.getRate());
        return "fail";
    }

    @GetMapping("/tryAcquireWithTimeout")
    public String tryAcquireWithTimeout(Integer count, Integer timeout) {
        if (limiter.tryAcquire(count, timeout, TimeUnit.SECONDS)) {
            log.info("Success, rate {}", limiter.getRate());
            return "success";
        }
        log.info("Rejected, rate {}", limiter.getRate());
        return "fail";
    }

    @GetMapping("/acquire")
    public String acquire(Integer count) {
        limiter.acquire(count);
        log.info("Success, rate {}", limiter.getRate());
        return "success";
    }
}

Nginx rate limiting

Define a shared memory zone and apply it in a location block. Example limits each IP to 1 request/s with a burst of 2:

limit_req_zone $binary_remote_addr zone=iplimit:20m rate=1r/s;

server {
    server_name www.test.com;
    location /access-limit/ {
        proxy_pass http://127.0.0.1:8080/;
        limit_req zone=iplimit burst=2 nodelay;
    }
}

Redis + Lua distributed limiting

Lua scripts run atomically inside Redis. The script increments a counter, sets a 1‑second TTL, and returns true if the request is allowed.

-- rateLimiter.lua
local key   = KEYS[1]
local limit = tonumber(ARGV[1])
local count = tonumber(redis.call('GET', key) or '0')
if count + 1 > limit then
    return false
else
    redis.call('INCRBY', key, 1)
    redis.call('EXPIRE', key, 1)
    return true
end

Spring integration:

@Service
@Slf4j
public class AccessLimiter {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedisScript<Boolean> rateLimitLua;

    public void limitAccess(String key, Integer limit) {
        Boolean allowed = redisTemplate.execute(rateLimitLua,
                Collections.singletonList(key),
                limit.toString());
        if (!allowed) {
            log.error("Access blocked, key={}", key);
            throw new RuntimeException("Access blocked");
        }
    }
}

Annotation‑based AOP

Define a custom annotation and an aspect that builds a unique key (method name + parameter types) and delegates to AccessLimiter.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimiterAop {
    int limit();
    String methodKey() default "";
}

@Aspect
@Component
@Slf4j
public class AccessLimiterAspect {
    @Autowired
    private AccessLimiter accessLimiter;

    @Pointcut("@annotation(com.example.AccessLimiterAop)")
    public void cut() {}

    @Before("cut()")
    public void before(JoinPoint joinPoint) {
        MethodSignature sig = (MethodSignature) joinPoint.getSignature();
        Method method = sig.getMethod();
        AccessLimiterAop ann = method.getAnnotation(AccessLimiterAop.class);
        if (ann == null) return;
        String key = ann.methodKey();
        if (StringUtils.isEmpty(key)) {
            key = method.getName() + "#" +
                  Arrays.stream(method.getParameterTypes())
                        .map(Class::getName)
                        .collect(Collectors.joining(","));
        }
        accessLimiter.limitAccess(key, ann.limit());
    }
}

Usage on a controller method:

@RestController
@Slf4j
public class TestController {
    @AccessLimiterAop(limit = 1)
    @GetMapping("/test")
    public String test() {
        return "success";
    }
}

Choosing an implementation

Guava RateLimiter : lightweight, client‑side, suitable for single‑instance services.

Nginx limit_req : enforces limits at the edge, works for any backend.

Redis + Lua : provides a centralized, atomic limiter for distributed clusters; can be combined with Spring AOP for declarative usage.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootNginxToken BucketRedis Lualeaky bucketdistributed rate limitingAPI idempotencyGuava RateLimiter
IT Architects Alliance
Written by

IT Architects Alliance

Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.

0 followers
Reader feedback

How this landed with the community

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.