apache/pulsar · error · IllegalArgumentException

timeout must not be negative

Error message

timeout must not be negative

What it means

ProcessingTimeoutPolicy rejects a negative Duration in its private constructor. A negative processing timeout is meaningless — it would represent a deadline already in the past — so the library fails fast with IllegalArgumentException instead of producing undefined redelivery behavior.

Source

Thrown at pulsar-client-api-v5/src/main/java/org/apache/pulsar/client/api/v5/config/ProcessingTimeoutPolicy.java:56

 * when the application's processing time is bounded and you want stalled deliveries to
 * be reattempted automatically.
 *
 * <p>Use {@link #of(Duration)} for the common case (no extra backoff), or
 * {@link #builder()} to also configure {@code redeliveryBackoff}.
 */
@EqualsAndHashCode
@ToString
public final class ProcessingTimeoutPolicy {

    private final Duration timeout;
    private final BackoffPolicy redeliveryBackoff;

    private ProcessingTimeoutPolicy(Duration timeout, BackoffPolicy redeliveryBackoff) {
        if (timeout == null) {
            throw new IllegalArgumentException("timeout must not be null");
        }
        if (timeout.isNegative()) {
            throw new IllegalArgumentException("timeout must not be negative");
        }
        this.timeout = timeout;
        this.redeliveryBackoff = redeliveryBackoff;
    }

    /**
     * @return how long the client waits for the application to ack a delivery before
     *         requesting redelivery; {@link Duration#ZERO} disables
     */
    public Duration timeout() {
        return timeout;
    }

    /**
     * @return optional backoff applied between redeliveries, or {@code null} for the
     *         default (no extra delay)
     */
    public BackoffPolicy redeliveryBackoff() {

View on GitHub (pinned to 820761864e)

Solutions

  1. Pass a non-negative Duration; use Duration.ZERO if 'no timeout' is intended (zero is explicitly allowed)
  2. Guard the value before building: if (d.isNegative()) throw new IllegalArgumentException(...) or clamp with a default

Example fix

// before
Duration timeout = Duration.between(lastSeen, Instant.now()); // can be negative
ProcessingTimeoutPolicy p = ProcessingTimeoutPolicy.builder().timeout(timeout).build();
// after
Duration timeout = Duration.between(lastSeen, Instant.now());
if (timeout.isNegative()) timeout = Duration.ZERO;
ProcessingTimeoutPolicy p = ProcessingTimeoutPolicy.builder().timeout(timeout).build();
Defensive patterns

Strategy: validation

Validate before calling

if (timeout != null && timeout.isNegative()) {
    throw new IllegalArgumentException("processing timeout must be non-negative, got: " + timeout);
}

Type guard

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

Try / catch

try {
    policy = ProcessingTimeoutPolicy.builder().timeout(timeout).build();
} catch (IllegalArgumentException e) {
    log.warn("Negative timeout rejected, falling back to default");
    policy = ProcessingTimeoutPolicy.builder().timeout(Duration.ofSeconds(30)).build();
}

Prevention

When it happens

Trigger: Passing a negative Duration (e.g. Duration.ofSeconds(-1), Duration.ofMillis(negativeValue)) to ProcessingTimeoutPolicy.builder().timeout(...) or the of() factory.

Common situations: Arithmetic on Durations producing negative results (earlier time minus later time), or misconfigured config values parsed as negative numbers and wrapped in Duration.of*().

Understand the failure class

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/aadd75a7b9774046. Report an issue: GitHub.