How to Implement API Duplicate Prevention and Idempotency in Spring Boot
This article explains how to prevent duplicate API calls and achieve idempotency in Spring Boot 3.0.5 by using unique tokens, timestamps, or Spring AOP, and provides complete code examples for custom annotations, AOP aspects, interceptors, and Redis‑based token management.
What is Interface Duplicate Prevention
Duplicate prevention ensures that an interface request can be executed only once within a certain time window, avoiding repeated submissions that could cause duplicate data or errors.
Methods
Use a unique identifier (e.g., a request token) and reject the request if the identifier has already been consumed.
Employ timestamps or counters to record request time or count and reject repeats within a defined range.
Apply Spring AOP to intercept the request at a specific method or layer and perform duplicate‑check logic.
These techniques help maintain system consistency and stability.
Idempotency vs Duplicate Prevention
Idempotency means that executing an API request multiple times yields the same result, while duplicate prevention focuses on allowing only a single execution within a time frame.
Idempotency Techniques
Include a unique request ID to recognize and block repeated processing.
Use optimistic or pessimistic locking to ensure data consistency.
For update operations, compare old and new data and proceed only when changes are detected.
Duplicate Prevention Techniques
Allow only one execution of an operation or request within a specified period, primarily to avoid repeated data submissions.
Technical Implementation
Method 1: AOP Approach
Custom Annotation
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventDuplicate {
/** Unique identifier key passed via header */
String header() default "token";
/** Unique identifier key passed via request parameter */
String param() default "token";
}Custom AOP Aspect
@Component
@Aspect
public class PreventDuplicateAspect {
public static final String PREVENT_PREFIX_KEY = "prevent:";
private final StringRedisTemplate stringRedisTemplate;
private final HttpServletRequest request;
public PreventDuplicateAspect(StringRedisTemplate stringRedisTemplate, HttpServletRequest request) {
this.stringRedisTemplate = stringRedisTemplate;
this.request = request;
}
@Around("@annotation(prevent)")
public Object preventDuplicate(ProceedingJoinPoint pjp, PreventDuplicate prevent) throws Throwable {
String key = prevent.header();
String value = null;
if (key != null && key.length() > 0) {
value = request.getHeader(key);
} else {
key = prevent.param();
if (key != null && key.length() > 0) {
value = request.getParameter(key);
}
}
if (value == null || "".equals(value.trim())) {
return "非法请求";
}
String prevent_key = PREVENT_PREFIX_KEY + value;
Boolean result = stringRedisTemplate.delete(prevent_key);
if (result != null && result.booleanValue()) {
return pjp.proceed();
} else {
return "请不要重复提交";
}
}
}Token Generation API
@RestController
@RequestMapping("/generate")
public class GenerateController {
private final StringRedisTemplate stringRedisTemplate;
public GenerateController(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@GetMapping("/token")
public String token() {
String token = UUID.randomUUID().toString().replace("-", "");
stringRedisTemplate.opsForValue().setIfAbsent(
PreventDuplicateAspect.PREVENT_PREFIX_KEY + token,
token,
5 * 60,
TimeUnit.SECONDS);
return token;
}
}Business API
@RestController
@RequestMapping("/prevent")
public class PreventController {
@PreventDuplicate
@GetMapping("/index")
public Object index() {
return "index success";
}
}Testing steps: first call the token‑generation endpoint to obtain a token, then call the business endpoint with the token. The first call succeeds; subsequent calls are blocked.
Method 2: Interceptor Approach
Custom Interceptor
@Component
public class PreventDuplicateInterceptor implements HandlerInterceptor {
private final StringRedisTemplate stringRedisTemplate;
public PreventDuplicateInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod hm) {
if (hm.hasMethodAnnotation(PreventDuplicate.class)) {
PreventDuplicate pd = hm.getMethodAnnotation(PreventDuplicate.class);
String key = pd.header();
String value = null;
if (key != null && key.length() > 0) {
value = request.getHeader(key);
} else {
key = pd.param();
if (key != null && key.length() > 0) {
value = request.getParameter(key);
}
}
if (value == null || "".equals(value.trim())) {
response.setContentType("text/plain;charset=utf-8");
response.getWriter().println("非法请求");
return false;
}
String prevent_key = PreventDuplicateAspect.PREVENT_PREFIX_KEY + value;
Boolean result = stringRedisTemplate.delete(prevent_key);
if (result != null && result.booleanValue()) {
return true;
} else {
response.setContentType("text/plain;charset=utf-8");
response.getWriter().println("请不要重复提交");
return false;
}
}
}
return true;
}
}Interceptor Configuration
@Component
public class PreventWebConfig implements WebMvcConfigurer {
private final PreventDuplicateInterceptor duplicateInterceptor;
public PreventWebConfig(PreventDuplicateInterceptor duplicateInterceptor) {
this.duplicateInterceptor = duplicateInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.duplicateInterceptor).addPathPatterns("/**");
}
}Testing the interceptor follows the same token acquisition and request flow; the first request succeeds, while repeated requests are rejected.
End of tutorial.
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.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.
