How to Prevent Duplicate Order Numbers in High-Concurrency Java Applications
This article analyzes a real incident where duplicate order numbers were generated under high concurrency, critiques the original timestamp‑based approach, and presents a thread‑safe Java solution using AtomicInteger, Java 8 date formatting, and optional IP suffixes to ensure globally unique identifiers.
Problem Overview
At the end of last year an incident occurred where the system generated duplicate order numbers under high concurrency, causing lookup errors and failed callbacks.
Original Implementation
The original code built order numbers using a timestamp (yyMMddHHmmssSSS), optional merchant ID, and a two‑digit random number. A test with 100 parallel executions produced 13 duplicates.
public static String getYYMMDDHHNumber(String merchId) { /* ... */ } public static String getRandomByLength(int size) { /* ... */ }Analysis of Issues
Only two random digits are insufficient for high concurrency.
Milliseconds are not unique across threads on multi‑core CPUs.
Merchant ID does not guarantee uniqueness.
Improved Design
The new design removes the merchant ID, shortens the millisecond part, and introduces a thread‑safe counter (AtomicInteger) that increments for each request. Java 8 DateTimeFormatter is used for thread‑safe date formatting.
private static final AtomicInteger SEQ = new AtomicInteger(1000);
private static final DateTimeFormatter DF_FMT_PREFIX = DateTimeFormatter.ofPattern("yyMMddHHmmssSS");
private static final ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai");
public static String generateOrderNo() {
LocalDateTime dataTime = LocalDateTime.now(ZONE_ID);
if (SEQ.intValue() > 9990) {
SEQ.getAndSet(1000);
}
return dataTime.format(DF_FMT_PREFIX) + SEQ.getAndIncrement();
}A further version adds the last two octets of the host IP to distinguish multiple instances running on the same machine.
private static volatile String IP_SUFFIX = null;
private static String getLocalIpSuffix() { /* ... */ }
public static String generateOrderNo() {
LocalDateTime dataTime = LocalDateTime.now(ZONE_ID);
if (SEQ.intValue() > 9990) { SEQ.getAndSet(1000); }
return dataTime.format(DF_FMT_PREFIX) + getLocalIpSuffix() + SEQ.getAndIncrement();
}Testing Results
Running 8 000 parallel calls produced zero duplicates, confirming the effectiveness of the new algorithm.
Recommendations
AtomicInteger provides lock‑free atomic increments; no additional synchronization is needed.
IP suffix logic is safe without double‑checked locking.
Choose a solution that fits the overall system architecture; alternatives include UUID, Redis, database sequences, or Snowflake algorithms.
Always include stress testing when modifying order‑number generation.
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.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
