apache/pulsar · error · IllegalArgumentException

Must set timeout unit for timeout.

Error message

Must set timeout unit for timeout.

What it means

BatchReceivePolicy.verify() additionally requires that if timeout is set (> 0), a timeoutUnit must also be provided; a timeout without a time unit is ambiguous and rejected with IllegalArgumentException. Internally getTimeoutMs() also silently yields 0 without a unit, so the check prevents a misleading no-timeout policy.

Source

Thrown at pulsar-client-api/src/main/java/org/apache/pulsar/client/api/BatchReceivePolicy.java:104

     * timeout for waiting for enough messages(enough number or enough bytes).
     */
    private final int timeout;
    private final TimeUnit timeoutUnit;


    /**
     * If it is false, one time `batchReceive()` only can receive the single topic messages,
     * the max messages and max size will not be strictly followed. (default: true).
     */
    private final boolean messagesFromMultiTopicsEnabled;

    public void verify() {
        if (maxNumMessages <= 0 && maxNumBytes <= 0 && timeout <= 0) {
            throw new IllegalArgumentException("At least "
                    + "one of maxNumMessages, maxNumBytes, timeout must be specified.");
        }
        if (timeout > 0 && timeoutUnit == null) {
            throw new IllegalArgumentException("Must set timeout unit for timeout.");
        }
    }

    public long getTimeoutMs() {
        return (timeout > 0 && timeoutUnit != null) ? timeoutUnit.toMillis(timeout) : 0L;
    }

    public int getMaxNumMessages() {
        return maxNumMessages;
    }

    public int getMaxNumBytes() {
        return maxNumBytes;
    }

    public boolean isMessagesFromMultiTopicsEnabled() {
        return messagesFromMultiTopicsEnabled;
    }

View on GitHub (pinned to 820761864e)

Solutions

  1. Use the builder's timeout(long, TimeUnit) convenience so unit and value are set together.
  2. If setting fields manually, also set timeoutUnit (e.g. TimeUnit.MILLISECONDS).
  3. Set timeout <= 0 if you truly want no timeout, so the check is skipped.

Example fix

// before
BatchReceivePolicy policy = BatchReceivePolicy.builder()
    .maxNumMessages(10)
    .timeout(100)
    .build();
// after
BatchReceivePolicy policy = BatchReceivePolicy.builder()
    .maxNumMessages(10)
    .timeout(100, TimeUnit.MILLISECONDS)
    .build();
Defensive patterns

Strategy: validation

Validate before calling

if (policy.getTimeout() > 0 && policy.getTimeoutUnit() == null) {
    throw new IllegalArgumentException("timeout set without timeoutUnit");
}
policy.verify();

Try / catch

try {
    policy.verify();
} catch (IllegalArgumentException e) {
    log.error("BatchReceivePolicy misconfigured: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Building a BatchReceivePolicy with timeout(N) (or a raw timeout field > 0) but never calling timeoutUnit/timeout(long, TimeUnit), then passing it to ConsumerBuilder.batchReceivePolicy().

Common situations: Using the builder's timeout(int) or setting fields directly on the policy without specifying the TimeUnit; deserializing policy from config where the unit field was omitted.

Understand the failure class

Related errors


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