How to Prevent Duplicate Submissions in Java APIs with Redis and Redisson Locks
This article explains why API debouncing is crucial for backend Java services, outlines which endpoints need protection, describes how to identify duplicate requests, and provides step‑by‑step implementations using Redis shared‑cache and Redisson distributed locks with concrete code examples and test results.
The author, an experienced backend Java developer, explains why API debouncing is essential to prevent duplicate submissions caused by user mistakes or network jitter.
Debounce is defined as preventing both user “hand shake” and network “shake”. The article lists interface types that typically need debouncing: user‑input APIs (e.g., search), button‑click APIs (e.g., form submit), and scroll‑load APIs (e.g., infinite scroll).
To decide whether two requests are duplicates, the author proposes three checks: a time interval threshold, comparison of key parameters, and optional URL comparison.
Distributed deployment strategies
Two concrete solutions are presented.
Shared cache : store a temporary key in Redis with SET_IF_ABSENT and an expiration time; if the key already exists the request is rejected.
Distributed lock : use Redisson’s RLock; acquire the lock with tryLock(), set the same expiration, and release it after the method finishes.
Both approaches rely on a unique lock key generated from annotated method parameters.
Implementation details
A custom @RequestLock annotation defines prefix, expiration, time unit and delimiter. Parameters marked with @RequestKeyParam are concatenated to form the key. The RequestKeyGenerator.getLockKey method reflects on method arguments and fields to build the key string.
For the Redis‑based solution, RedisRequestLockAspect uses StringRedisTemplate.execute with RedisCallback to call SET_IF_ABSENT. If the call returns false, a BizException with message “Your operation is too fast, please try later” is thrown.
For the Redisson‑based solution, RedissonRequestLockAspect obtains an RLock via redissonClient.getLock(lockKey), attempts tryLock(), sets the lock expiration, proceeds with the original method, and finally unlocks.
Test screenshots show the first request succeeding ("Add user success"), rapid repeated submissions being blocked with error BIZ‑0001, and later submissions succeeding after the lock expires.
The author notes that the lock alone does not guarantee idempotency; database unique constraints (e.g., a unique index on userPhone) and business‑level checks are still required. He also advises adding user‑specific information (user ID, IP) to the key in production to avoid false positives.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
