apache/pulsar · error · IllegalArgumentException

timeout must be > 0

Error message

timeout must be > 0

What it means

TransactionPolicy requires a strictly positive Duration for the transaction timeout. The timeout determines how long a transaction may remain open before the coordinator aborts it; null, zero, or negative values cannot define a valid deadline, so the private constructor throws IllegalArgumentException (null is caught by the preceding requireNonNull).

Source

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

import java.util.Objects;
import lombok.EqualsAndHashCode;
import lombok.ToString;

/**
 * Transaction configuration for the Pulsar client.
 *
 * <p>Construct via {@link #builder()}.
 */
@EqualsAndHashCode
@ToString
public final class TransactionPolicy {

    private final Duration timeout;

    private TransactionPolicy(Duration timeout) {
        Objects.requireNonNull(timeout, "timeout must not be null");
        if (timeout.isNegative() || timeout.isZero()) {
            throw new IllegalArgumentException("timeout must be > 0");
        }
        this.timeout = timeout;
    }

    /**
     * @return transaction timeout — if the transaction is not committed or aborted within this duration,
     *         the broker automatically aborts it
     */
    public Duration timeout() {
        return timeout;
    }

    /**
     * @return a new builder for constructing a {@link TransactionPolicy}
     */
    public static Builder builder() {
        return new Builder();
    }

View on GitHub (pinned to 820761864e)

Solutions

  1. Pass a positive duration, e.g. .timeout(Duration.ofMinutes(1)) on the TransactionPolicy builder
  2. If the value comes from config, default to a positive constant when it is missing or <= 0
  3. Confirm the configured seconds/minutes value is > 0 before wrapping it in a Duration

Example fix

// before
long seconds = config.getProperty("txn.timeout", 0);
TransactionPolicy p = TransactionPolicy.builder().timeout(Duration.ofSeconds(seconds)).build();
// after
long seconds = config.getProperty("txn.timeout", 60);
if (seconds <= 0) seconds = 60;
TransactionPolicy p = TransactionPolicy.builder().timeout(Duration.ofSeconds(seconds)).build();
Defensive patterns

Strategy: validation

Validate before calling

if (timeout == null || timeout.isNegative() || timeout.isZero()) {
    throw new IllegalArgumentException("transaction timeout must be a positive Duration, got: " + timeout);
}

Type guard

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

Try / catch

try {
    policy = TransactionPolicy.builder().timeout(timeout).build();
} catch (IllegalArgumentException e) {
    log.error("Invalid transaction timeout: {}", e.getMessage());
    policy = TransactionPolicy.builder().timeout(Duration.ofMinutes(1)).build();
}

Prevention

When it happens

Trigger: Passing Duration.ZERO or a negative Duration to TransactionPolicy's builder/factory; passing null produces the preceding 'timeout must not be null' NPE instead.

Common situations: Config values parsed as 0 (unset property defaulting to 0 seconds), or code computing a timeout that rounds down to zero.

Understand the failure class

Related errors


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