apache/pulsar · error · IllegalArgumentException

timeout must not be null

Error message

timeout must not be null

What it means

ProcessingTimeoutPolicy's private constructor validates that the processing timeout is non-null before creating the policy object. The Apache Pulsar v5 client API requires an explicit, finite timeout for message processing/redelivery; a null timeout would leave the policy in an undefined state, so the builder rejects it eagerly with IllegalArgumentException.

Solutions

  1. Call .timeout(Duration.ofSeconds(30)) (or your desired duration) on the ProcessingTimeoutPolicy builder before build()
  2. If the timeout comes from configuration, supply a default when the config value is missing, e.g. Objects.requireNonNullElse(configured, Duration.ofSeconds(30))

Example fix

// before
ProcessingTimeoutPolicy policy = ProcessingTimeoutPolicy.builder().build();
// after
ProcessingTimeoutPolicy policy = ProcessingTimeoutPolicy.builder()
        .timeout(Duration.ofSeconds(30))
        .build();
Defensive patterns

Strategy: validation

Validate before calling

if (timeout == null) {
    throw new IllegalArgumentException("ProcessingTimeoutPolicy requires a timeout; pass e.g. Duration.ofSeconds(30)");
}
ProcessingTimeoutPolicy.builder().timeout(timeout).build();

Type guard

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

Try / catch

try {
    policy = ProcessingTimeoutPolicy.builder().timeout(timeout).build();
} catch (IllegalArgumentException e) {
    log.error("Invalid processing timeout policy: {}", e.getMessage());
    policy = ProcessingTimeoutPolicy.builder().timeout(Duration.ofSeconds(30)).build();
}

Prevention

When it happens

Trigger: Calling ProcessingTimeoutPolicy.builder().build() or of(null) (any factory/builder path) without ever setting a timeout via .timeout(Duration) so that the private constructor receives a null Duration.

Common situations: Developers copying a builder snippet but omitting the .timeout(...) call, or passing a Duration field that is null because it was read from an unset config property.

Understand the failure class

Related errors


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

Appendix: source

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

 *
 * <p>Disabled by default. Pass to
 * {@link org.apache.pulsar.client.api.v5.QueueConsumerBuilder#processingTimeout(ProcessingTimeoutPolicy)}
 * 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

View on GitHub (pinned to 820761864e)