Backend Development 11 min read

Improving Order Number Generation to Avoid Duplicates in High‑Concurrency Java Applications

The article analyzes a real‑world incident where duplicate order IDs were generated under high concurrency, demonstrates the shortcomings of the original Java implementation, and presents a thread‑safe redesign using AtomicInteger, Java 8 date‑time API and container IP suffix to guarantee unique identifiers across parallel requests and clustered instances.

Top Architect
Top Architect
Top Architect
Improving Order Number Generation to Avoid Duplicates in High‑Concurrency Java Applications

At the end of last year an online incident occurred where the system produced two identical order numbers with different contents, causing query errors and failed callbacks; the problem repeated multiple times, prompting a redesign of the order‑number generation logic.

The original implementation concatenated a prefix "OD", a timestamp formatted with new SimpleDateFormat("yyMMddHHmmssSSS") , a merchant ID (up to three digits) and a two‑digit random number, resulting in a 22‑character order ID. The random part was too short and the millisecond precision proved insufficient under high concurrency, leading to duplicate IDs.

A quick stress test with 100 parallel calls demonstrated the issue:

public static void main(String[] args) {
    final String merchId = "12334";
    List
orderNos = Collections.synchronizedList(new ArrayList<>());
    IntStream.range(0, 100).parallel().forEach(i -> {
        orderNos.add(getYYMMDDHHNumber(merchId));
    });
    List
filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList());
    System.out.println("生成订单数:" + orderNos.size());
    System.out.println("过滤重复后订单数:" + filterOrderNos.size());
    System.out.println("重复订单数:" + (orderNos.size() - filterOrderNos.size()));
}

The output showed 13 duplicate IDs out of 100, confirming the flaw.

To fix the problem the following changes were made:

Remove the merchant‑ID parameter (it did not prevent duplicates).

Keep only three digits of the millisecond part to shorten the length while still providing variability.

Introduce a thread‑safe counter (AtomicInteger) that increments for each request; three digits guarantee uniqueness for up to 800 concurrent calls, and four digits were used in the code.

Replace the legacy SimpleDateFormat with Java 8's DateTimeFormatter for thread safety and cleaner code.

The revised implementation looks like this:

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 larger stress test with 8,000 parallel executions confirmed zero duplicates:

public static void main(String[] args) {
    List
orderNos = Collections.synchronizedList(new ArrayList<>());
    IntStream.range(0, 8000).parallel().forEach(i -> {
        orderNos.add(generateOrderNo());
    });
    List
filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList());
    System.out.println("生成订单数:" + orderNos.size());
    System.out.println("过滤重复后订单数:" + filterOrderNos.size());
    System.out.println("重复订单数:" + (orderNos.size() - filterOrderNos.size()));
}

While the new code eliminates duplicates within a single JVM, a clustered deployment could still generate collisions. Several strategies were considered, including UUID initialization, Redis‑based auto‑increment, database sequences, using the host IP, port number, Snowflake algorithm, or process ID.

Because the services run inside Docker containers on the same physical host, the author chose to embed the container's IP suffix into the order number. The final version adds the IP suffix before the atomic counter:

private static volatile String IP_SUFFIX = null;
private static String getLocalIpSuffix() {
    if (IP_SUFFIX != null) return IP_SUFFIX;
    synchronized (OrderGen2Test.class) {
        if (IP_SUFFIX != null) return IP_SUFFIX;
        try {
            InetAddress addr = InetAddress.getLocalHost();
            String hostAddress = addr.getHostAddress();
            if (hostAddress != null && hostAddress.length() > 4) {
                String ipSuffix = hostAddress.trim().split("\\.")[3];
                if (ipSuffix.length() == 2) {
                    IP_SUFFIX = ipSuffix;
                } else {
                    ipSuffix = "0" + ipSuffix;
                    IP_SUFFIX = ipSuffix.substring(ipSuffix.length() - 2);
                }
                return IP_SUFFIX;
            }
        } catch (Exception e) {
            System.out.println("获取IP失败:" + e.getMessage());
        }
        IP_SUFFIX = RandomUtils.nextInt(10, 20) + "";
        return IP_SUFFIX;
    }
}

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();
}

Running the same 8,000‑parallel test with the IP‑aware generator produced zero duplicates, confirming its suitability for a clustered environment.

Key take‑aways:

The generateOrderNo() method does not need explicit locking because AtomicInteger provides CAS‑based atomicity.

The singleton pattern used in getLocalIpSuffix() avoids unnecessary synchronization after the first initialization.

Multiple solutions exist for distributed unique ID generation; the best choice depends on the specific architecture.

Thorough testing is essential; the author’s colleague previously missed testing, leading to production issues.

Promotional notes about the "Top Architecture" public account, QR codes, and community groups have been omitted from this technical summary.

backenddistributed systemsJavaConcurrencyunique identifierorder ID
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

login 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.