Implementing API Idempotency in Spring Boot Using Tokens, Database Primary Keys, Optimistic Locks, and Redis

This article explains the concept of idempotency in computing and HTTP, why it is essential for APIs, the impact on system design, which RESTful methods are idempotent, and presents four practical implementation strategies—including database primary keys, optimistic locking, anti‑repeat tokens, and downstream sequence numbers—accompanied by complete Spring Boot code examples and testing procedures.

Top Architect
Top Architect
Top Architect
Implementing API Idempotency in Spring Boot Using Tokens, Database Primary Keys, Optimistic Locks, and Redis

1. What Is Idempotency

Idempotency is a mathematical and computing concept where applying an operation multiple times yields the same result as applying it once.

2. HTTP Idempotency

In HTTP/1.1, an idempotent request produces the same effect on a resource no matter how many times it is repeated, except for network time‑outs.

3. Why Idempotency Is Needed

It prevents duplicate submissions caused by front‑end repeated clicks, malicious repeated actions, client retries, or message re‑consumption, thereby simplifying client logic and protecting system state.

4. Impact on the System

Introducing idempotency can increase server‑side complexity: parallel operations may need to be serialized, additional business logic is required, and extra storage (e.g., Redis) is often used.

5. Idempotency of RESTful Methods

Method

Idempotent?

Description

GET

Retrieves resources without side effects.

POST

Creates new resources; each call adds data.

PUT

May be idempotent if it overwrites a fixed value; not if it performs accumulative updates.

DELETE

Idempotent when deleting by unique identifier; not when deleting by condition.

6. How to Implement Idempotency

The article presents four practical schemes:

Database Unique Primary Key : Use a globally unique primary key (often a distributed ID) to guarantee a single insert or delete.

Database Optimistic Lock : Add a version field to rows; updates succeed only when the version matches.

Anti‑Repeat Token : Generate a UUID token, store it in Redis with a short TTL, and validate/delete it atomically via a Lua script.

Downstream Sequence Number : Let the downstream service generate a unique sequence number, combine it with a credential ID, and use the pair as a Redis key for deduplication.

7. Code Example – Anti‑Repeat Token Scheme

Below is a complete Spring Boot example that demonstrates the token approach.

7.1 Maven Dependencies (pom.xml)

<span style="color:#c17d37;"><?xml version="1.0" encoding="UTF-8"?></span>
<span style="color:#0c9ce5;"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"></span>
    ...
<span style="color:#0c9ce5;"></project></span>

7.2 Redis Configuration (application.yml)

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    timeout: 1000
    lettuce:
      pool:
        max-active: 100
        max-wait: -1
        min-idle: 0
        max-idle: 20

7.3 Token Utility Service (TokenUtilService.java)

<span style="color:#c17d37;">import</span> java.util.UUID;
<span style="color:#c17d37;">import</span> java.util.concurrent.TimeUnit;
<span style="color:#c17d37;">import</span> org.springframework.beans.factory.annotation.Autowired;
<span style="color:#c17d37;">import</span> org.springframework.data.redis.core.StringRedisTemplate;
<span style="color:#c17d37;">import</span> org.springframework.data.redis.core.script.DefaultRedisScript;
<span style="color:#c17d37;">import</span> org.springframework.stereotype.Service;

@Service
public class TokenUtilService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    private static final String IDEMPOTENT_TOKEN_PREFIX = "idempotent_token:";

    /** Generate a token and store it in Redis for 5 minutes */
    public String generateToken(String value) {
        String token = UUID.randomUUID().toString();
        String key = IDEMPOTENT_TOKEN_PREFIX + token;
        redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);
        return token;
    }

    /** Validate the token atomically using a Lua script */
    public boolean validToken(String token, String value) {
        String script = "if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        String key = IDEMPOTENT_TOKEN_PREFIX + token;
        Long result = redisTemplate.execute(redisScript, java.util.Collections.singletonList(key), value);
        return result != null && result != 0L;
    }
}

7.4 Controller (TokenController.java)

<span style="color:#c17d37;">import</span> org.springframework.web.bind.annotation.*;

@RestController
public class TokenController {
    @Autowired
    private TokenUtilService tokenService;

    @GetMapping("/token")
    public String getToken() {
        String userInfo = "mydlq"; // example user info
        return tokenService.generateToken(userInfo);
    }

    @PostMapping("/test")
    public String test(@RequestHeader("token") String token) {
        String userInfo = "mydlq";
        boolean ok = tokenService.validToken(token, userInfo);
        return ok ? "正常调用" : "重复调用";
    }
}

7.5 Spring Boot Application (Application.java)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

7.6 Test Class (IdempotenceTest.java)

import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest
public class IdempotenceTest {
    @Autowired
    private WebApplicationContext wac;

    @Test
    public void interfaceIdempotenceTest() throws Exception {
        MockMvc mvc = MockMvcBuilders.webAppContextSetup(wac).build();
        String token = mvc.perform(MockMvcRequestBuilders.get("/token")
                .accept(MediaType.TEXT_HTML)).andReturn().getResponse().getContentAsString();
        for (int i = 1; i <= 5; i++) {
            String result = mvc.perform(MockMvcRequestBuilders.post("/test")
                    .header("token", token)
                    .accept(MediaType.TEXT_HTML)).andReturn().getResponse().getContentAsString();
            if (i == 1) {
                Assert.assertEquals("正常调用", result);
            } else {
                Assert.assertEquals("重复调用", result);
            }
        }
    }
}

8. Summary

Idempotency is crucial for services that handle money or state changes. Choose the appropriate scheme based on business needs: primary‑key for insert/delete, optimistic lock for updates, downstream sequence numbers for distributed calls, and token‑Redis for generic duplicate‑submission protection. Proper implementation ensures reliable, repeat‑safe API behavior.

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.

JavadatabaseredisSpring BootAPIIdempotencyToken
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

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.