apache/kafka · error · IllegalArgumentException

Timeout needs to be greater than 0

Error message

Timeout needs to be greater than 0

What it means

Thrown as IllegalArgumentException by NetworkClientUtils.awaitReady when the supplied timeoutMs is negative. awaitReady blocks on the non-blocking NetworkClient until the node is ready, so a negative timeout is treated as a programming error rather than a wait request. The check runs before any polling, so the exception is raised immediately on entry.

Source

Thrown at clients/src/main/java/org/apache/kafka/clients/NetworkClientUtils.java:61

        client.poll(0, currentTime);
        return client.isReady(node, currentTime);
    }

    /**
     * Invokes `client.poll` to discard pending disconnects, followed by `client.ready` and 0 or more `client.poll`
     * invocations until the connection to `node` is ready, the timeoutMs expires or the connection fails.
     *
     * It returns `true` if the call completes normally or `false` if the timeoutMs expires. If the connection fails,
     * an `IOException` is thrown instead. Note that if the `NetworkClient` has been configured with a positive
     * connection timeoutMs, it is possible for this method to raise an `IOException` for a previous connection which
     * has recently disconnected. If authentication to the node fails, an `AuthenticationException` is thrown.
     *
     * This method is useful for implementing blocking behaviour on top of the non-blocking `NetworkClient`, use it with
     * care.
     */
    public static boolean awaitReady(KafkaClient client, Node node, Time time, long timeoutMs) throws IOException {
        if (timeoutMs < 0) {
            throw new IllegalArgumentException("Timeout needs to be greater than 0");
        }
        long startTime = time.milliseconds();

        if (isReady(client, node, startTime) ||  client.ready(node, startTime))
            return true;

        long attemptStartTime = time.milliseconds();
        while (!client.isReady(node, attemptStartTime) && attemptStartTime - startTime < timeoutMs) {
            if (client.connectionFailed(node)) {
                throw new IOException("Connection to " + node + " failed.");
            }
            long pollTimeout = timeoutMs - (attemptStartTime - startTime); // initialize in this order to avoid overflow

            // If the network client is waiting to send data for some reason (eg. throttling or retry backoff),
            // polling longer than that is potentially dangerous as the producer will not attempt to send
            // any pending requests.
            long waitingTime = client.pollDelayMs(node, startTime);
            if (waitingTime > 0 && pollTimeout > waitingTime) {

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Pass a non-negative timeout; use Long.MAX_VALUE (or a very large value) for an effectively-unbounded wait instead of -1.
  2. Compute timeout defensively as Math.max(0, deadline - now) at the call site.
  3. Validate externalized timeout configs at startup and fail fast with a clearer error.
  4. Review the caller chain that produced the negative value; it usually indicates the deadline already expired upstream.

Example fix

// before
long remaining = deadlineMs - System.currentTimeMillis();
NetworkClientUtils.awaitReady(client, node, time, remaining);
// after
long remaining = Math.max(0, deadlineMs - System.currentTimeMillis());
NetworkClientUtils.awaitReady(client, node, time, remaining);
Defensive patterns

Strategy: validation

Validate before calling

// Validate the timeout before calling NetworkClientUtils.awaitReady(client, node, time, timeoutMs).
public static long awaitReadySafe(KafkaClient client, Node node, Time time, long timeoutMs) throws java.io.IOException {
    if (timeoutMs < 0) {
        throw new IllegalArgumentException(
            "timeoutMs must be >= 0; got " + timeoutMs
            + ". Use 0 for a non-blocking readiness check.");
    }
    return NetworkClientUtils.awaitReady(client, node, time, timeoutMs) ? timeoutMs : -1L;
}

Type guard

// Force timeouts through a non-negative typed wrapper.
public final class NonNegativeTimeout {
    public final long ms;
    public NonNegativeTimeout(long ms) {
        if (ms < 0) throw new IllegalArgumentException("timeout must be >= 0, got " + ms);
        this.ms = ms;
    }
}
// Usage: new NonNegativeTimeout(userTimeout).ms

Try / catch

try {
    NetworkClientUtils.awaitReady(client, node, time, timeoutMs);
} catch (IllegalArgumentException e) {
    if ("Timeout needs to be greater than 0".equals(e.getMessage())) {
    // Programming bug, not a runtime condition. Clamp to 0 or surface a config error;
    // do not retry with the same value.
    timeoutMs = Math.max(0, timeoutMs);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling NetworkClientUtils.awaitReady(client, node, time, timeoutMs) with timeoutMs < 0. This is invoked by callers that wrap the non-blocking client in blocking semantics (some admin/inner client paths). A negative value comes from arithmetic underflow, a misconfigured timeout, or a caller passing a sentinel such as -1.

Common situations: Caller computes timeout as (deadline - now) where now is past the deadline, yielding a negative; passing a configured 'unlimited' sentinel of -1 into awaitReady instead of Long.MAX_VALUE; misconfigured request.timeout.ms / reconnect backoff expressed as a negative; tests that pass a literal negative.

Related errors


AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03). Data as JSON: /data/errors/1954cf205a5144ae.json. Report an issue: GitHub.