shuzheng/zheng · critical · RuntimeException

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

Error message

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

What it means

nextId() compares the current timestamp with lastTimestamp; if time has gone backwards it throws RuntimeException refusing to generate ids for the negative interval, because reusing a timestamp could produce duplicate snowflake ids. The message includes how many milliseconds the clock moved backwards.

Source

Thrown at zheng-common/src/main/java/com/zheng/common/util/key/SnowflakeIdWorker.java:118

			throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
		}
		this.workerId = workerId;
		this.datacenterId = datacenterId;
	}

	// ==============================Methods==========================================

	/**
	 * 获得下一个ID (该方法是线程安全的)
	 *
	 * @return SnowflakeId
	 */
	public synchronized long nextId() {
		long timestamp = timeGen();

		//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
		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) & sequenceMask;
			//毫秒内序列溢出
			if (sequence == 0) {
				//阻塞到下一个毫秒,获得新的时间戳
				timestamp = tilNextMillis(lastTimestamp);
			}
		}
		//时间戳改变,毫秒内序列重置
		else {
			sequence = 0L;
		}

		//上次生成ID的时间截

View on GitHub (pinned to 7005c0a775)

Solutions

  1. Fix the underlying clock source and let it catch up; the service will resume once current time exceeds lastTimestamp.
  2. Enable gradual (slew) NTP corrections instead of step changes (e.g. ntpd without -g steps, chrony slew mode).
  3. Restart the service only if the clock regression is permanent and ensure workerId/datacenterId differs, or persist lastTimestamp and reject duplicates on restart.
  4. Wrap id generation so the RuntimeException is handled: queue requests briefly or fail over to another node whose clock is monotonic.

Example fix

// before
long id = idWorker.nextId(); // throws after clock rollback
// after
try {
    long id = idWorker.nextId();
} catch (RuntimeException e) {
    if (e.getMessage().startsWith("Clock moved backwards")) {
        // wait for clock to catch up or fail over
    } else {
        throw e;
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-check before generating
long ts = System.currentTimeMillis();
// if a previously persisted lastTimestamp is newer than ts, clock regressed
if (ts < lastKnownTimestamp) {
    log.warn("Clock moved backwards by {} ms; delaying id generation", lastKnownTimestamp - ts);
}

Type guard

boolean clockIsMonotonic(long lastTimestamp) {
    return System.currentTimeMillis() >= lastTimestamp;
}

Try / catch

long id;
while (true) {
    try { id = idWorker.nextId(); break; }
    catch (RuntimeException e) {
        if (e.getMessage() != null && e.getMessage().startsWith("Clock moved backwards")) {
            Thread.sleep(100); // wait for clock to catch up
        } else throw e;
    }
}

Prevention

When it happens

Trigger: Calling nextId() after the system clock was set back (manual change, NTP correction, VM snapshot restore, leap adjustment) so timeGen() returns a value smaller than the previously used lastTimestamp.

Common situations: VM/host restored from snapshot; NTP daemon stepping the clock backwards; container migrated to a host with skewed clock; operator manually correcting a too-fast clock.

Related errors


AI-assisted analysis of shuzheng/zheng@7005c0a775 (2026-09-04). Data as JSON: /api/errors/d454b950784e88f9. Report an issue: GitHub.