DEV Community

Tiger Smith
Tiger Smith

Posted on • Edited on • Originally published at it-res.com

Why Your Buttons Get Spammed with Clicks? A Spring Boot Guide to Prevent Duplicate Submissions

Introduction: Why Do Users Keep Spamming Your Buttons?

Picture this: A user hammers the “Submit Order” button like a kid playing an arcade game—only to trigger 5 identical orders in your system. Next thing you know, customer service lines are blowing up, your database is cluttered with junk data, and you’re woken up at 2 AM to roll back the system. That’s the “horror story” of duplicate submissions.

Duplicate submissions are essentially a chemical reaction of network latency + user impatience. Preventing them isn’t just a technical need—it’s a humanitarian effort to save programmers’sleep! This article will walk you through building a “bronze-to-king” anti-duplicate system with Spring Boot, using a progressive approach from local locks to distributed locks.

1. Basic Approach: Local Lock Annotation (Single-Server Setup)

1.1 Implement a Custom Annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {int lockTime() default 3; // Lock lasts 3s by default; should be longer than frontend button disable time
}
Enter fullscreen mode Exit fullscreen mode

1.2 Implement Lock Logic with AOP

@Aspect
@Component
public class RepeatSubmitAspect {// Use ConcurrentHashMap as local lock storage (NOTE: Fails in distributed environments!)
    private static final ConcurrentHashMap<String, Object> LOCKS = new ConcurrentHashMap<>();

    @Around("@annotation(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint point, NoRepeatSubmit noRepeatSubmit) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        // Generate a unique lock key: User ID + API path (use more complex rules in production)
        String lockKey = getUserId(request) + "-" + request.getServletPath();

        // If lock already exists, throw an exception (custom business exceptions recommended)
        if (LOCKS.putIfAbsent(lockKey, Boolean.TRUE) != null) {throw new RuntimeException("Too fast! Slow down a bit."); // User-friendly message
        }

        try {return point.proceed(); // Execute the actual business method
        } finally {// Release lock with delay (ALWAYS use finally to ensure release!)
            Thread.sleep(noRepeatSubmit.lockTime() * 1000);
            LOCKS.remove(lockKey);
        }
    }

    // In production, parse User ID from token; simplified demo here
    private String getUserId(HttpServletRequest request) {return Optional.ofNullable(request.getHeader("Authorization"))
                .orElse("anonymous");
    }
}
Enter fullscreen mode Exit fullscreen mode

1.3 Usage Example: Order Creation API

@RestController
public class OrderController {@NoRepeatSubmit(lockTime = 5) // Block duplicate calls for 5 seconds
    @PostMapping("/createOrder")
    public ResponseEntity createOrder(@RequestBody OrderDTO order) {// Actual order creation logic (usually requires transaction management)
        orderService.create(order);
        return ResponseEntity.ok("Order created successfully");
    }
}
Enter fullscreen mode Exit fullscreen mode

⚠️ Rant Alert:This works for 90% of duplicate submission cases in single-server setups—but it completely breaks in clustered deployments. Why? Because each server’s is isolated, so locks on one server won’t block requests to other servers. Let’s fix that with a distributed solution!

2. Advanced Approach: Redis Distributed Lock (Clustered Setup)

Continue reading: Why Your Buttons Get Spammed with Clicks? A Spring Boot Guide to Prevent Duplicate Submissions

Top comments (0)