Backend Development 16 min read

Implementing Interface Debounce (Anti‑duplicate Submission) in Java Backend with Redis and Redisson

This article explains how to prevent duplicate API calls in Java backend systems by using debounce techniques, shared Redis caches or Redisson distributed locks, and provides complete annotation‑based implementations with code examples, key generation strategies, and testing results to achieve idempotent interfaces.

Top Architect
Top Architect
Top Architect
Implementing Interface Debounce (Anti‑duplicate Submission) in Java Backend with Redis and Redisson

As a senior Java developer, the author shares practical experience designing multi‑tenant backend systems and emphasizes the importance of preventing duplicate submissions (debounce) both for user errors and network instability.

Debounce means preventing repeated requests caused by user mistakes or network jitter; the frontend can show a loading state, but the backend must also enforce debounce logic.

An ideal debounce component should be logically correct, fast, easy to integrate, decoupled from business logic, and provide clear user feedback.

Which APIs Need Debounce?

User input APIs such as search or form fields.

Button click APIs like submit or save actions.

Scroll‑load APIs such as pull‑to‑refresh or infinite scrolling.

How to Identify Duplicate Requests?

Determine duplication by setting a time window, comparing key parameters, and optionally comparing request URLs.

Distributed Deployment Solutions

Shared Cache (Redis)

Use a Redis key with an expiration and the SET_IF_ABSENT option to ensure only the first request succeeds.

@PostMapping("/add")
@RequiresPermissions(value = "add")
@Log(methodDesc = "添加用户")
public ResponseEntity
add(@RequestBody AddReq addReq) {
    return userService.add(addReq);
}

Distributed Lock (Redisson)

Acquire a Redisson lock, set an expiration, and release it after processing.

RLock lock = redissonClient.getLock(lockKey);
if (!lock.tryLock()) {
    throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,请稍后重试");
}
lock.lock(requestLock.expire(), requestLock.timeUnit());
try {
    return joinPoint.proceed();
} finally {
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

Implementation Details

Define a @RequestLock annotation to specify lock prefix, expiration, time unit, and delimiter. Use @RequestKeyParam on method parameters or fields to build a unique lock key.

package com.example.requestlock.lock.annotation;

@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestKeyParam {}

The RequestKeyGenerator extracts annotated parameters or fields via reflection to construct the lock key.

public static String getLockKey(ProceedingJoinPoint joinPoint) {
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    RequestLock requestLock = method.getAnnotation(RequestLock.class);
    Object[] args = joinPoint.getArgs();
    Parameter[] parameters = method.getParameters();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < parameters.length; i++) {
        RequestKeyParam keyParam = parameters[i].getAnnotation(RequestKeyParam.class);
        if (keyParam != null) {
            sb.append(requestLock.delimiter()).append(args[i]);
        }
    }
    // fallback to field annotations if needed
    // ... (omitted for brevity)
    return requestLock.prefix() + sb;
}

Two AOP aspects implement the debounce logic:

RedisRequestLockAspect uses StringRedisTemplate.execute with SET_IF_ABSENT to set a lock key.

RedissonRequestLockAspect obtains a Redisson lock and handles lock acquisition failures.

Testing Results

First submission succeeds, rapid repeated submissions are blocked with error "BIZ-0001:您的操作太快了,请稍后重试", and after the lock expires the request succeeds again.

Debounce works, but true idempotency also requires business‑level checks such as unique constraints in the database.

The article concludes with recommendations to include user‑specific information (e.g., user ID, IP) in the lock key and to combine debounce with database uniqueness constraints for full idempotency.

Additional promotional sections about ChatGPT services, private groups, and related offers are present but are not part of the technical solution.

backendJavaRedisDistributed LockIdempotencyRedissonDebounce
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.