apache/shenyu · critical · RuntimeException

Clock moved backwards. Refusing to generate id for

Error message

Clock moved backwards.  Refusing to generate id for %d milliseconds

What it means

UUIDUtils.nextId() is a Snowflake-style ID generator that encodes the current millisecond timestamp into each ID. If the system clock is set backwards (NTP correction, VM snapshot restore, manual change) below the last timestamp used, it throws this RuntimeException to prevent generating duplicate or non-monotonic IDs.

Solutions

  1. Wait for the wall clock to catch back up past lastTimestamp (offset reported in the message), then retry ID generation
  2. Re-sync the clock with NTP (e.g. chronyc makestep / ntpdate) ensuring it never moves backwards, or use slewing instead of stepping
  3. Restart the application process — this resets the in-memory lastTimestamp, but only do so after fixing the clock to avoid duplicate IDs
  4. Long-term: run instances with clock-sync guarantees (disable aggressive NTP step, avoid snapshot restores of running JVMs)

Example fix

// before
try {
    long id = UUIDUtils.getInstance().getGeneratedKey();
} catch (RuntimeException e) {
    // process dies or request fails on clock jump
}
// after
long id;
try {
    id = UUIDUtils.getInstance().getGeneratedKey();
} catch (RuntimeException e) {
    if (!e.getMessage().startsWith("Clock moved backwards")) throw e;
    TimeUnit.MILLISECONDS.sleep(estimateCatchUpMs(e)); // or wait via monitoring
    id = UUIDUtils.getInstance().getGeneratedKey();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before generating: check clock is not behind a previously persisted max timestamp
long last = loadLastPersistedTimestamp();
if (System.currentTimeMillis() < last) {
    throw new IllegalStateException("Clock behind last issued id timestamp by " + (last - System.currentTimeMillis()) + " ms");
}

Try / catch

try {
    long id = UUIDUtils.getInstance().getGeneratedKey();
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Clock moved backwards")) {
        // wait/retry after clock re-sync
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling UUIDUtils.getInstance().getGeneratedKey() (or any ID-generation path) after the JVM clock jumps backwards relative to the last timestamp recorded in nextId().

Common situations: NTP daemon stepping the clock back on a host running the gateway or admin; restoring a VM/container snapshot; clock drift between host and hypervisor after live migration; manual time change during testing.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/6825cc9b8ae32dab. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-common/src/main/java/org/apache/shenyu/common/utils/UUIDUtils.java:87

        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.idepoch = idepoch;
    }

    /**
     * Gets instance.
     *
     * @return the instance
     */
    public static UUIDUtils getInstance() {
        return ID_WORKER_UTILS;
    }

    private synchronized long nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - idepoch) << TIMESTAMP_LEFT_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT) | sequence;
    }

    private long tilNextMillis(final long lastTimestamp) {

View on GitHub (pinned to 567142e072)