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

  1. Use a delay of at least 1 ms, e.g. Math.max(1, configuredDelay)
  2. To run as soon as possible, use vertx.runOnContext(handler) instead of a 0-delay timer
  3. Guard config-derived values before scheduling: if (delayMs < 1) delayMs = 1; or skip scheduling entirely
  4. 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

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


AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/6d7198b947710a19. Report an issue: GitHub.