eclipse-vertx/vert.x · error · IllegalArgumentException

Cannot schedule a timer with initialDelay < 0

Error message

Cannot schedule a timer with initialDelay < 0

What it means

Vert.x validates the initialDelay parameter of periodic timer scheduling and throws IllegalArgumentException when it is negative. A negative initial delay has no meaning in the underlying scheduler, so it is rejected before the timer is registered. Note delay itself must also be >= 1 ms (separate check).

Source

Thrown at vertx-core/src/main/java/io/vertx/core/impl/VertxImpl.java:693

      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;
  }

  public long scheduleTimeout(ContextInternal context,
                                              boolean periodic,

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Clamp: initialDelay = Math.max(0, computedInitialDelay) before calling setPeriodic
  2. Fix the subtraction order so delay = deadline - now, and skip scheduling when the deadline already passed
  3. Validate the config value at load time and reject/normalize negatives early

Example fix

// before
long initial = deadline - System.currentTimeMillis();
vertx.setPeriodic(initial, 1000, TimeUnit.MILLISECONDS, h -> tick()); // negative if deadline passed
// after
long initial = Math.max(0, deadline - System.currentTimeMillis());
vertx.setPeriodic(initial, 1000, TimeUnit.MILLISECONDS, h -> tick());
Defensive patterns

Strategy: validation

Validate before calling

if (initialDelayMs < 0) {
  initialDelayMs = 0; // or skip scheduling
}

Try / catch

try {
  vertx.setPeriodic(initialDelayMs, periodMs, TimeUnit.MILLISECONDS, handler);
} catch (IllegalArgumentException e) {
  // normalize initialDelay and retry once
}

Prevention

When it happens

Trigger: vertx.setPeriodic(initialDelay, delay, unit, handler) or the InternalTimerHandler scheduling path with initialDelay < 0, typically from a config-computed value like (deadline - now).

Common situations: Computing an initial delay from timestamps where the deadline already passed (now > deadline yields negative); misordered subtraction (deadline - now vs now - deadline); negative config values.

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/aa21826760fa0c39. Report an issue: GitHub.