Implementing API Idempotency with Spring Boot, Redis, and Custom Annotations
This article explains the concept of idempotency, presents several strategies to achieve it, and provides a complete Spring Boot implementation using Redis, a custom @AutoIdempotent annotation, token generation and verification, interceptor configuration, and test cases to ensure only the first request succeeds.
In real-world development, an exposed API may receive many requests, so ensuring idempotency—where multiple executions have the same effect as a single execution—is essential. Common techniques include unique database indexes, token mechanisms, pessimistic/optimistic locks, and pre‑check‑then‑execute logic.
Redis can be used to implement automatic idempotency. First, a Redis server is set up and accessed via Spring Boot’s RedisTemplate. The following utility class provides basic operations such as set, setEx, exists, get, and remove:
@Component
public class RedisService {
@Autowired
private RedisTemplate redisTemplate;
public boolean set(final String key, Object value) { /* ... */ }
public boolean setEx(final String key, Object value, Long expireTime) { /* ... */ }
public boolean exists(final String key) { return redisTemplate.hasKey(key); }
public Object get(final String key) { /* ... */ }
public boolean remove(final String key) { /* ... */ }
}A custom annotation @AutoIdempotent is defined to mark methods that require automatic idempotency:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {}The token service interface declares methods for creating and checking tokens:
public interface TokenService {
String createToken();
boolean checkToken(HttpServletRequest request) throws Exception;
}Its implementation generates a UUID token, stores it in Redis with a configurable expiration, and validates the token on subsequent requests, removing it after successful verification:
@Service
public class TokenServiceImpl implements TokenService {
@Autowired
private RedisService redisService;
public String createToken() { /* generate UUID, store in Redis */ }
public boolean checkToken(HttpServletRequest request) throws Exception { /* retrieve, validate, delete */ }
}The interceptor configuration adds AutoIdempotentInterceptor to the Spring MVC interceptor chain. The interceptor checks whether the handler method is annotated with @AutoIdempotent and, if so, invokes TokenService.checkToken. On failure, it writes a JSON error response.
@Component
public class AutoIdempotentInterceptor implements HandlerInterceptor {
@Autowired
private TokenService tokenService;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) return true;
Method method = ((HandlerMethod) handler).getMethod();
AutoIdempotent ann = method.getAnnotation(AutoIdempotent.class);
if (ann != null) {
try { return tokenService.checkToken(request); }
catch (Exception ex) { /* write JSON error */ throw ex; }
}
return true;
}
// postHandle and afterCompletion omitted for brevity
private void writeReturnJson(HttpServletResponse response, String json) throws Exception { /* ... */ }
}A Web configuration class registers the interceptor:
@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
@Resource
private AutoIdempotentInterceptor autoIdempotentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(autoIdempotentInterceptor);
super.addInterceptors(registry);
}
}Test cases demonstrate the workflow: a client first calls /get/token to obtain a token, then calls a protected endpoint annotated with @AutoIdempotent. The first request succeeds; subsequent requests with the same token are rejected as duplicate operations.
In summary, the blog shows how to combine Spring Boot, Redis, custom annotations, and interceptors to achieve elegant API idempotency, preventing duplicate data modifications and reducing unnecessary load.
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.
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.
