eclipse-vertx/vert.x · error · IllegalArgumentException
Cannot schedule a timer with delay < 1 ms
Error message
Cannot schedule a timer with delay < 1 ms
What it means
Vert.x rejects timer scheduling when the delay (or period) is less than 1 millisecond. Internally timers map onto Netty HashedWheelTimer/EventLoop schedules which cannot honor sub-millisecond periods, so scheduleTimer validates delay >= 1 before creating the InternalTimerHandler. The error is an IllegalArgumentException thrown synchronously on the calling thread.
Source
Thrown at vertx-core/src/main/java/io/vertx/core/impl/VertxImpl.java:690
DnsAddressResolverProvider provider = DnsAddressResolverProvider.create(this, addressResolverOptions);
InetSocketAddress address = provider.nameServerAddresses().get(0);
// provide the host and port
options = new DnsClientOptions(options)
.setHost(address.getAddress().getHostAddress())
.setPort(address.getPort());
}
return new DnsClientImpl(this, options);
}
private long scheduleTimeout(ContextInternal context,
boolean periodic,
long initialDelay,
long delay,
TimeUnit timeUnit,
boolean addCloseHook,
Handler<Long> handler) {
if (delay < 1) {
throw new IllegalArgumentException("Cannot schedule a timer with delay < 1 ms");
}
if (initialDelay < 0) {
throw new IllegalArgumentException("Cannot schedule a timer with initialDelay < 0");
}
long timerId = timeoutCounter.getAndIncrement();
InternalTimerHandler task = new InternalTimerHandler(timerId, handler, periodic, context);
timeouts.put(timerId, task);
if (addCloseHook) {
context.addCloseHook(task);
}
EventLoop el = context.nettyEventLoop();
if (periodic) {
task.future = el.scheduleAtFixedRate(task, initialDelay, delay, timeUnit);
} else {
task.future = el.schedule(task, delay, timeUnit);
}
return task.id;
}View on GitHub (pinned to fb308bd8c3)
Solutions
- Use a delay of at least 1 ms, e.g. Math.max(1, configuredDelay)
- To run as soon as possible, use vertx.runOnContext(handler) instead of a 0-delay timer
- Guard config-derived values before scheduling: if (delayMs < 1) delayMs = 1; or skip scheduling entirely
- Check unit conversion: sub-millisecond intervals are unsupported; pick a different mechanism
Example fix
// before vertx.setPeriodic(0, id -> poll()); // IllegalArgumentException // after vertx.runOnContext(v -> poll()); // immediate vertx.setPeriodic(Math.max(1, configuredDelayMs), id -> poll());
Defensive patterns
Strategy: validation
Validate before calling
if (delayMs < 1) {
throw new IllegalArgumentException("delay must be >= 1 ms, got " + delayMs);
} Try / catch
try {
vertx.setPeriodic(delayMs, handler);
} catch (IllegalArgumentException e) {
// fall back to minimum delay or immediate runOnContext
} Prevention
- Clamp config-derived delays with Math.max(1, delay)
- Use vertx.runOnContext for immediate execution instead of 0 delay
- Check unit conversions that can truncate to 0
When it happens
Trigger: vertx.setTimer(0, handler), vertx.setPeriodic(0, handler), or any periodic scheduling whose delay parameter is 0 or negative, including through setPeriodic(initialDelay, delay, unit, handler) with delay < 1 in the given TimeUnit.
Common situations: Passing a config value of 0 meaning 'no delay' or 'disabled'; converting from another unit where rounding yields 0 (e.g. 500 microseconds to ms); defaulting an unset config to 0; a busy-poll retry loop intended to run immediately.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Cannot schedule a timer with initialDelay < 0
- Invalid reset code value
- Invalid window size: ${windowSize}
- Ping data must be exactly 8 bytes
- Invalid window size: ${windowSize}
AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06).
Data as JSON: /api/errors/6d7198b947710a19.
Report an issue: GitHub.