Implementing Idempotency in Spring Boot Using Redis and Custom Annotations
This article explains the concept of idempotency, identifies which HTTP operations are naturally idempotent, describes why idempotency is needed in scenarios like timeout retries, async callbacks and message queues, and provides a complete Spring Boot implementation using a custom annotation, Redis storage, AOP interception, token generation and example controllers.
Idempotency ensures that multiple identical requests produce the same result as a single request, which is crucial for reliable backend services.
Requests that are naturally idempotent include query operations and most delete operations, while updates and inserts can be non‑idempotent unless special handling such as unique constraints is used.
The article discusses three main scenarios that require idempotency: timeout retries, asynchronous callbacks, and message‑queue consumption, each of which may deliver duplicate requests.
To achieve idempotency, the article proposes using a unique token (idempotent token) generated per request, stored in a persistent store such as Redis, and validated before processing.
A custom Java annotation @Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { String name() default ""; String field() default ""; Class type(); } is defined with attributes for the parameter name, field, and type. A data‑transfer object public class RequestData { private Header header; private T body; } public class Header { private String token; } containing a Header with the token is used as the method argument.
An AOP aspect @Aspect @Component public class IdempotentAspect { @Resource private RedisIdempotentStorage redisIdempotentStorage; @Pointcut("@annotation(com.springboot.micrometer.annotation.Idempotent)") public void idempotent() {} @Around("idempotent()") public Object methodAround(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Idempotent idempotent = method.getAnnotation(Idempotent.class); String field = idempotent.field(); String name = idempotent.name(); Class clazzType = idempotent.type(); String token = ""; Object object = clazzType.newInstance(); Map paramValue = AopUtils.getParamValue(joinPoint); if (object instanceof RequestData) { RequestData idempotentEntity = (RequestData) paramValue.get(name); token = String.valueOf(AopUtils.getFieldValue(idempotentEntity.getHeader(), field)); } if (redisIdempotentStorage.delete(token)) { return joinPoint.proceed(); } return "重复请求"; } } intercepts methods annotated with @Idempotent , extracts the token, checks Redis via RedisIdempotentStorage , and either proceeds with the method or returns a duplicate‑request response.
A token‑generation controller provides an endpoint to obtain a token, which is saved in Redis with a short TTL: @RestController @RequestMapping("/idGenerator") public class IdGeneratorController { @Resource private RedisIdempotentStorage redisIdempotentStorage; @RequestMapping("/getIdGeneratorToken") public String getIdGeneratorToken() { String generateId = IdGeneratorUtil.generateId(); redisIdempotentStorage.save(generateId); return generateId; } } The business controller then uses the token in the request header and applies the @Idempotent annotation to enforce single execution: @RestController @RequestMapping("/order") public class OrderController { @RequestMapping("/saveOrder") @Idempotent(name = "requestData", type = RequestData.class, field = "token") public String saveOrder(@RequestBody RequestData requestData) { return "success"; } }
Example request flow: obtain token → call /order/saveOrder with token → first call succeeds, second call with the same token is rejected, demonstrating idempotent behavior.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.