apache/kafka · error · IllegalArgumentException

The timeout cannot be negative.

Error message

The timeout cannot be negative.

What it means

An IllegalArgumentException thrown by KafkaAdminClient.close(Duration) when the supplied timeout converts to a negative millisecond value. A negative Duration cannot be honored as a wait and indicates a caller bug.

Solutions

  1. Clamp the computed timeout to zero before calling close: admin.close(duration.compareTo(Duration.ZERO) < 0 ? Duration.ZERO : duration).
  2. Use Admin.DURATION which is non-negative, or pass Duration.ZERO for a non-blocking close.
  3. Compute remaining time with Math.max(0, deadline - now).

Example fix

// before
long remaining = deadlineMs - System.currentTimeMillis(); // can be < 0
admin.close(Duration.ofMillis(remaining));

// after
long remaining = Math.max(0, deadlineMs - System.currentTimeMillis());
admin.close(Duration.ofMillis(remaining));
Defensive patterns

Strategy: validation

Validate before calling

Duration safe = timeout.isNegative() ? Duration.ZERO : timeout;
admin.close(safe);

Type guard

static boolean isNonNegative(Duration d) {
    return d == null || !d.isNegative();
}

Prevention

When it happens

Trigger: Calling admin.close(Duration.ofMillis(-1)) or admin.close(Duration.ofNanos(-100)). Most commonly, subtracting two timestamps and passing the (negative) difference as the close timeout.

Common situations: Computing remaining time before a deadline and passing it without clamping at zero; reusing a deadline Duration that has already elapsed; off-by-one in retry-loop timeout arithmetic.

Understand the failure class

Related errors


AI-assisted analysis of apache/kafka@996fb4585a (2026-08-11). Data as JSON: /api/errors/a4ad16685d2b0156. Report an issue: GitHub.

Appendix: source

Thrown at clients/src/main/java/org/apache/kafka/clients/admin/KafkaAdminClient.java:726

        if (defaultApiTimeoutMs < requestTimeoutMs) {
            if (config.originals().containsKey(AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG)) {
                throw new ConfigException("The specified value of " + AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG +
                    " must be no smaller than the value of " + AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG + ".");
            } else {
                log.warn("Overriding the default value for {} ({}) with the explicitly configured request timeout {}",
                    AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG, defaultApiTimeoutMs,
                    requestTimeoutMs);
                return requestTimeoutMs;
            }
        }
        return defaultApiTimeoutMs;
    }

    @Override
    public void close(Duration timeout) {
        long waitTimeMs = timeout.toMillis();
        if (waitTimeMs < 0)
            throw new IllegalArgumentException("The timeout cannot be negative.");
        waitTimeMs = Math.min(TimeUnit.DAYS.toMillis(365), waitTimeMs); // Limit the timeout to a year.
        long now = time.milliseconds();
        long newHardShutdownTimeMs = now + waitTimeMs;
        long prev = INVALID_SHUTDOWN_TIME;
        clientTelemetryReporter.ifPresent(ClientTelemetryReporter::initiateClose);
        metrics.close();
        while (true) {
            if (hardShutdownTimeMs.compareAndSet(prev, newHardShutdownTimeMs)) {
                if (prev == INVALID_SHUTDOWN_TIME) {
                    log.debug("Initiating close operation.");
                } else {
                    log.debug("Moving hard shutdown time forward.");
                }
                client.wakeup(); // Wake the thread, if it is blocked inside poll().
                break;
            }
            prev = hardShutdownTimeMs.get();
            if (prev < newHardShutdownTimeMs) {

View on GitHub (pinned to 996fb4585a)