Implementing Request Debounce in Java Backend Using Redis and Redisson
This article explains how to prevent duplicate submissions in Java backend services by implementing request debouncing using custom annotations, Redis-based locks, and Redisson distributed locks, covering key generation, lock acquisition, configuration, and testing to ensure idempotent API calls.
Introduction
The author, a backend Java developer with six years of experience, shares practical techniques for preventing duplicate submissions (debounce) in web APIs, emphasizing both client‑side and server‑side safeguards.
What Is Debounce?
Debounce protects against user‑mistakes and network jitter that can cause the same request to be processed multiple times, potentially creating duplicate data records.
Design Requirements for a Debounce Component
Logical correctness – no false positives.
Fast response – minimal latency.
Easy integration – business logic decoupled.
Clear user feedback – e.g., "You clicked too fast".
Which Interfaces Need Debounce?
User‑input interfaces such as search boxes and form fields.
Button‑click interfaces like form submission or settings save.
Scroll‑loading interfaces such as pull‑to‑refresh or infinite scroll.
How to Identify Duplicate Requests
Determine duplication by a time interval, comparing key parameters, and optionally the request URL.
Distributed Deployment Solutions
1. Shared Cache (Redis)
Use a Redis key with a short TTL; the SET_IF_ABSENT option ensures only the first request succeeds.
StringRedisTemplate.execute(connection ->
connection.set(lockKey.getBytes(), new byte[0],
Expiration.from(requestLock.expire(), requestLock.timeUnit()),
RedisStringCommands.SetOption.SET_IF_ABSENT));2. Distributed Lock (Redisson)
Redisson provides a lock object that can be tried and, if acquired, held for a configurable period.
RLock lock = redissonClient.getLock(lockKey);
boolean isLocked = lock.tryLock();
if (!isLocked) { throw new BizException(...); }
lock.lock(requestLock.expire(), requestLock.timeUnit());Implementation Details
A custom annotation @RequestLock defines lock prefix, expiration time, time unit, and key delimiter.
public @interface RequestLock {
String prefix() default "";
int expire() default 2;
TimeUnit timeUnit() default TimeUnit.SECONDS;
String delimiter() default "&";
}Parameters that should contribute to the lock key are marked with @RequestKeyParam . The RequestKeyGenerator builds the final key by concatenating annotated parameter values.
public static String getLockKey(ProceedingJoinPoint joinPoint) {
// ... reflection logic to collect @RequestKeyParam values ...
return requestLock.prefix() + sb;
}The AOP aspects RedisRequestLockAspect and RedissonRequestLockAspect intercept methods annotated with @RequestLock , acquire the appropriate lock, execute the original method, and release the lock.
Testing Results
First submission succeeds – "User added successfully".
Rapid repeated submissions are blocked – "Your operation is too fast, please try later".
After the lock expires, a new submission succeeds.
The tests confirm that debounce works, but true idempotency also requires database unique constraints and additional business checks.
Conclusion
By combining custom annotations, Redis or Redisson locks, and careful key generation, backend services can effectively prevent duplicate requests, improve data integrity, and provide a better user experience.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.