baomidou/mybatis-plus · 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
Sequence.nextId() is mybatis-plus's Snowflake-style ID generator. If the current timestamp is older than the last issued timestamp (clock went backwards), it tolerates at most 5 ms: it sleeps 2x the offset and re-reads the clock. This site throws RuntimeException('Clock moved backwards... N milliseconds') when, after that wait, time is STILL behind lastTimestamp — i.e. the small drift did not recover. Refusing to generate keeps IDs monotonic and collision-free.
Source
Thrown at mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/Sequence.java:180
return id;
}
/**
* 获取下一个 ID
*
* @return 下一个 ID
*/
public synchronized long nextId() {
long timestamp = timeGen();
//闰秒
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset <= 5) {
try {
Thread.sleep(offset << 1);
timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
}
}
if (lastTimestamp == timestamp) {
// 相同毫秒内,序列号自增
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 同一毫秒的序列数已经达到最大
timestamp = tilNextMillis(lastTimestamp);
}
} else {
// 不同毫秒内,序列号置为 1 - 2 随机数View on GitHub (pinned to bf67d90747)
Solutions
- Ensure NTP/chronyd runs in slew mode (gradual adjustment) rather than step mode on hosts generating IDs.
- Avoid VM snapshot/resume patterns for ID-generating services, or re-sync time immediately after resume.
- If backward steps are expected in your environment, set mybatis-plus's IdentifierGenerator to a custom implementation (e.g. one that waits until time passes lastTimestamp instead of throwing).
- Retry the insert once the clock has moved past lastTimestamp (a few ms) — the exception is transient by nature.
Example fix
// before: default snowflake throws on small backward clock steps
@IdType(IdType.ASSIGN_ID)
private Long id;
// after: tolerate clock skew by supplying a patient generator
@Configuration
public class IdConfig {
@Bean
public IdentifierGenerator identifierGenerator() {
return new IdentifierGenerator() {
private final Sequence seq = new Sequence();
@Override
public Long nextId(Object entity) {
long ts, last;
do { ts = System.currentTimeMillis(); } while (ts < (last = lastSeen.get()) && !lastSeen.compareAndSet(last, ts));
lastSeen.set(ts);
return seq.nextId();
}
private final AtomicLong lastSeen = new AtomicLong();
};
}
} Defensive patterns
Strategy: retry
Validate before calling
long now = System.currentTimeMillis();
long lastIssued = readLastIssuedTimestamp();
if (now < lastIssued) {
// wait out the skew before inserting
Thread.sleep(lastIssued - now);
} Try / catch
for (int attempt = 0; attempt < 3; attempt++) {
try {
return insert(entity); // ASSIGN_ID snowflake insert
} catch (RuntimeException e) {
if (!isClockMovedBackwards(e) || attempt == 2) throw e;
sleepQuietly(10L << attempt);
}
} Prevention
- Run NTP/chrony in slew-only mode on ID-generating hosts.
- Monitor for backward clock steps and alert before they hit the ID generator.
- Register a custom IdentifierGenerator that waits out bounded skew instead of throwing.
When it happens
Trigger: System clock steps backwards by 1-5 ms and does not catch up within 2x the offset sleep (continued backward drift, e.g. NTP slewing downward, VM pause/resume, or a host clock being actively corrected). Sequence.nextId() is called during this window (any insert that assigns an ASSIGN_ID primary key).
Common situations: NTP stepping the clock back; virtual machines resumed from snapshot/suspend with stale clocks; containers inheriting a corrected host clock mid-request; bare-metal hosts after manual date changes.
AI-assisted analysis of baomidou/mybatis-plus@bf67d90747 (2026-08-14).
Data as JSON: /api/errors/1b8391a497949e4e.
Report an issue: GitHub.