Ensuring API Idempotency with Spring Boot, Redis, and Token Interceptor

This article explains the concept of idempotency, lists common solutions, and demonstrates a practical implementation using Spring Boot, Redis, and a token‑based mechanism with custom annotations and interceptors, including full code examples, testing steps, and important pitfalls to avoid.

Programmer DD
Programmer DD
Programmer DD
Ensuring API Idempotency with Spring Boot, Redis, and Token Interceptor

Concept

Idempotency means that multiple identical requests must result in only one operation, such as preventing duplicate order creation, double payment, or repeated form submissions.

Common Solutions

Unique index to avoid dirty data

Token mechanism to block repeated submissions

Pessimistic lock (table or row lock)

Optimistic lock based on version number

Distributed lock using Redis (Jedis, Redisson) or Zookeeper

State machine to control status changes

Implementation Overview

The article adopts the token mechanism with Redis to achieve idempotency.

Implementation Idea

For each request that requires idempotency, generate a unique token, store it in Redis, and include the token in the request header or parameters. The interceptor checks if the token exists in Redis; if it does, the request proceeds and the token is deleted. If the token is missing, the request is rejected with a "please do not repeat the operation" message.

Project Overview

Spring Boot

Redis @ApiIdempotent annotation + interceptor @ControllerAdvice for global exception handling

JMeter for load testing

Code Implementation

// pom.xml dependencies (Redis‑Jedis, Lombok, etc.)
// JedisUtil utility class with methods: set, setex, get, del, exists, expire, ttl, close
// Custom annotation
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {}
// ApiIdempotentInterceptor implements HandlerInterceptor
@Autowired private TokenService tokenService;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    if (!(handler instanceof HandlerMethod)) return true;
    Method method = ((HandlerMethod) handler).getMethod();
    if (method.getAnnotation(ApiIdempotent.class) != null) {
        tokenService.checkToken(request);
    }
    return true;
}
// TokenServiceImpl
public ServerResponse createToken() {
    String str = RandomUtil.UUID32();
    String token = Constant.Redis.TOKEN_PREFIX + str;
    jedisUtil.set(token, token, Constant.Redis.EXPIRE_TIME_MINUTE);
    return ServerResponse.success(token);
}
public void checkToken(HttpServletRequest request) {
    String token = request.getHeader(TOKEN_NAME);
    if (StringUtils.isBlank(token)) token = request.getParameter(TOKEN_NAME);
    if (StringUtils.isBlank(token)) throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg());
    if (!jedisUtil.exists(token)) throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());
    Long del = jedisUtil.del(token);
    if (del <= 0) throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());
}
// TestApplication (SpringBootApplication) registers CorsFilter and the interceptor bean
@Bean public ApiIdempotentInterceptor apiIdempotentInterceptor() { return new ApiIdempotentInterceptor(); }
// TokenController provides an endpoint to obtain a token
@GetMapping public ServerResponse token() { return tokenService.createToken(); }
// TestController demonstrates an idempotent API
@ApiIdempotent
@PostMapping("testIdempotence")
public ServerResponse testIdempotence() { return testService.testIdempotence(); }

Testing

After obtaining a token, use JMeter to simulate 50 concurrent requests with the token as a header or parameter. Requests without a valid token are rejected.

Important Points (Critical)

When deleting the token, always verify the delete result. Failing to check can cause race conditions where multiple threads think the token still exists, leading to duplicate processing.

Summary

The approach ensures each request is unique by using a Redis‑backed token, simplifying idempotency handling through a custom annotation and interceptor, and can also be implemented with Spring AOP.

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.

APIInterceptorIdempotencyToken
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.