apache/dubbo · error · IllegalArgumentException

ticksPerWheel must be greater than 0: {}

Error message

ticksPerWheel must be greater than 0: {}

What it means

IllegalArgumentException thrown by the HashedWheelTimer constructor when ticksPerWheel is <= 0. The wheel size (number of buckets) determines how timeouts are distributed across the hash wheel — it must be a positive integer. The constructor checks this at the top level before delegating to createWheel(). A non-positive wheel size makes hash-based scheduling impossible.

Source

Thrown at dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java:241

     * @throws NullPointerException     if either of {@code threadFactory} and {@code unit} is {@code null}
     * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is &lt;= 0
     */
    public HashedWheelTimer(
        ThreadFactory threadFactory,
        long tickDuration, TimeUnit unit, int ticksPerWheel,
        long maxPendingTimeouts) {

        if (threadFactory == null) {
            throw new NullPointerException("threadFactory");
        }
        if (unit == null) {
            throw new NullPointerException("unit");
        }
        if (tickDuration <= 0) {
            throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);
        }
        if (ticksPerWheel <= 0) {
            throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);
        }

        // Normalize ticksPerWheel to power of two and initialize the wheel.
        wheel = createWheel(ticksPerWheel);
        mask = wheel.length - 1;

        // Convert tickDuration to nanos.
        this.tickDuration = unit.toNanos(tickDuration);

        // Prevent overflow.
        if (this.tickDuration >= Long.MAX_VALUE / wheel.length) {
            throw new IllegalArgumentException(String.format(
                "tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
                tickDuration, Long.MAX_VALUE / wheel.length));
        }
        workerThread = threadFactory.newThread(worker);

        this.maxPendingTimeouts = maxPendingTimeouts;

View on GitHub (pinned to 3a3043227f)

Solutions

  1. Set ticksPerWheel to a positive power of two (e.g., 512, the Dubbo/Netty default) — it is normalized internally but should be positive.
  2. Validate the value before construction and default to 512 if invalid.
  3. Use the convenience constructors that default ticksPerWheel to 512 (e.g., HashedWheelTimer(factory, tickDuration, unit)).

Example fix

// before
int wheelSize = config.getWheelSize(); // 0 if unset
new HashedWheelTimer(factory, 100, TimeUnit.MILLISECONDS, wheelSize);

// after
int wheelSize = config.getWheelSize();
if (wheelSize <= 0) wheelSize = 512;
new HashedWheelTimer(factory, 100, TimeUnit.MILLISECONDS, wheelSize);
Defensive patterns

Strategy: validation

Validate before calling

if (ticksPerWheel <= 0) {
    throw new IllegalArgumentException("ticksPerWheel must be positive, got: " + ticksPerWheel);
}
int safeWheel = ticksPerWheel > 0 ? ticksPerWheel : 512; // default
new HashedWheelTimer(factory, tickDuration, unit, safeWheel);

Prevention

When it happens

Trigger: Passing a ticksPerWheel of 0 or negative to the HashedWheelTimer constructor. Common when the value comes from unvalidated configuration or an arithmetic expression that underflows.

Common situations: Configuration property for wheel size set to 0 or left unset; computed wheel size that evaluates to non-positive due to a formula error; passing 0 intentionally to mean 'default' without realizing it's invalid.

Related errors


AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14). Data as JSON: /api/errors/5ae13f65c1fc9656. Report an issue: GitHub.