Understanding and Implementing Idempotency in Backend APIs with Spring Boot and Redis

This article explains the concept of idempotency, why it is needed for HTTP interfaces, the impact on system design, and presents several practical backend solutions—including database unique keys, optimistic locking, anti‑repeat tokens, and downstream sequence numbers—accompanied by complete Spring Boot code examples using Redis for token management.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding and Implementing Idempotency in Backend APIs with Spring Boot and Redis

1. What Is Idempotency

Idempotency is a mathematical and computer‑science concept where applying an operation multiple times yields the same result as applying it once; in programming, an idempotent operation produces identical effects no matter how many times it is executed with the same parameters.

2. Interface Idempotency

HTTP/1.1 defines idempotent methods as those that return the same result for repeated requests (excluding network errors). The side‑effects of a request should occur only on the first execution; subsequent identical requests must not change the resource state.

3. Why Implement Idempotency

Repeated submissions can happen due to front‑end double clicks, malicious repeated voting, client‑side timeout retries, or message‑queue duplicate consumption, leading to data inconsistency or incorrect business outcomes.

4. System Impact of Idempotency

While idempotency simplifies client logic and prevents duplicate actions, it adds server‑side complexity, such as converting parallel operations to serial ones and introducing extra business logic.

5. Idempotency in RESTful APIs

Among HTTP methods, GET, HEAD, PUT, DELETE, OPTIONS, and TRACE are idempotent (✓), while POST is not (✗). Some methods may be idempotent depending on business logic.

6. How to Implement Idempotency

Solution 1: Database Unique Primary Key

Use a globally unique primary key (often a distributed ID) to guarantee that an insert or delete operation can only occur once.

Applicable operations: insert, delete.

Limitation: requires generation of a globally unique ID.

INSERT INTO table (id, ...) VALUES (unique_id, ...);

Solution 2: Optimistic Locking

Add a version column to the table and include the version in the WHERE clause of an UPDATE; the update succeeds only if the version matches, preventing duplicate updates.

Applicable operation: update.

UPDATE my_table SET price = price + 50, version = version + 1 WHERE id = 1 AND version = 5;

Solution 3: Anti‑Repeat Token

Generate a token (e.g., UUID) on the client, store it in Redis with an associated user value, and validate the token atomically using a Lua script before processing the request.

Applicable operations: insert, update, delete.

import java.util.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

public class TokenUtilService {
    private static final String PREFIX = "idempotent_token:";
    @Autowired private StringRedisTemplate redisTemplate;
    public String generateToken(String value) {
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(PREFIX + token, value, 5, TimeUnit.MINUTES);
        return token;
    }
    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";
        RedisScript<Long> rs = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(rs, Arrays.asList(PREFIX + token, value));
        return result != null && result != 0L;
    }
}

Solution 4: Downstream Unique Sequence Number

The downstream service generates a short‑lived unique sequence number (or order ID) and sends it with the request; the upstream service uses the combination of this sequence number and a credential ID as a Redis key to detect duplicates.

7. Example Implementation

The following Spring Boot project demonstrates the anti‑repeat token approach.

7.1 Maven Dependencies

<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
    </parent>
    <dependencies>
        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
        <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
    </dependencies>
    <build>...</build>
</project>

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 Service (code shown above)

7.4 Controller

@RestController
public class TokenController {
    @Autowired private TokenUtilService tokenService;
    @GetMapping("/token")
    public String getToken() {
        String userInfo = "mydlq"; // mock 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 ? "Normal Call" : "Duplicate Call";
    }
}

7.5 Application Entry Point

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

7.6 Test Case

public class IdempotenceTest {
    @Autowired private WebApplicationContext wac;
    @Test public void interfaceIdempotenceTest() throws Exception {
        MockMvc mvc = MockMvcBuilders.webAppContextSetup(wac).build();
        String token = mvc.perform(get("/token")).andReturn().getResponse().getContentAsString();
        for (int i = 1; i <= 5; i++) {
            String result = mvc.perform(post("/test").header("token", token)).andReturn().getResponse().getContentAsString();
            System.out.println("Attempt " + i + ": " + result);
        }
    }
}

The test output shows the first request succeeds ("Normal Call") and subsequent requests are rejected as duplicates ("Duplicate Call").

8. Final Summary

Idempotency is essential for services such as payments and order processing. Depending on the business scenario, developers can choose between unique‑key insertion, optimistic locking, downstream sequence numbers, or token‑based anti‑repeat mechanisms. Properly understanding the requirements and applying the appropriate technique ensures data consistency and reliable system 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.

Backend DevelopmentSpring BootREST APIToken
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

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.