apache/pulsar · error · IllegalArgumentException

Message timeout can only be specified with processing guaran

Error message

Message timeout can only be specified with processing guarantee is ATLEAST_ONCE

What it means

Message timeout redelivery semantics are only supported with ATLEAST_ONCE processing guarantees. doCommonChecks throws this IllegalArgumentException when both timeoutMs is set and processingGuarantees is a value other than ATLEAST_ONCE (e.g. ATMOST_ONCE or EFFECTIVELY_ONCE), because timeouts conflict with those guarantee modes.

Source

Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java:866

            Boolean windowAutoAck = functionConfig.getAutoAck();
            if (windowAutoAck != null && windowAutoAck) {
                throw new IllegalArgumentException("Cannot enable auto ack when using windowing functionality");
            }
            WindowConfigUtils.validate(windowConfig);
        }

        if (functionConfig.getResources() != null) {
            ResourceConfigUtils.validate(functionConfig.getResources());
        }

        if (functionConfig.getTimeoutMs() != null && functionConfig.getTimeoutMs() <= 0) {
            throw new IllegalArgumentException("Function timeout must be a positive number");
        }

        if (functionConfig.getTimeoutMs() != null
                && functionConfig.getProcessingGuarantees() != null
                && functionConfig.getProcessingGuarantees() != FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE) {
            throw new IllegalArgumentException("Message timeout can only be specified with processing guarantee is "
                    + FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE.name());
        }

        if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0
                && functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {
            throw new IllegalArgumentException("MaxMessageRetries and Effectively once don't gel well");
        }
        if ((functionConfig.getMaxMessageRetries() == null || functionConfig.getMaxMessageRetries() < 0)
                && !org.apache.commons.lang3.StringUtils.isEmpty(functionConfig.getDeadLetterTopic())) {
            throw new IllegalArgumentException("Dead Letter Topic specified, however max retries is set to infinity");
        }
        if (functionConfig.getRetainKeyOrdering() != null
                && functionConfig.getRetainKeyOrdering()
                && functionConfig.getProcessingGuarantees() != null
                && functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {
            throw new IllegalArgumentException(
                    "When effectively once processing guarantee is specified, retain Key ordering cannot be set");
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Remove/clear timeoutMs when using EFFECTIVELY_ONCE or ATMOST_ONCE
  2. Or set processingGuarantees back to ATLEAST_ONCE if timeout behavior is required
  3. Audit merged config generation so timeout and guarantee are set consistently

Example fix

// before
config.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE);
config.setTimeoutMs(30000L);
// after
config.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE);
// timeout removed
config.setTimeoutMs(null);
Defensive patterns

Strategy: validation

Validate before calling

if (config.getTimeoutMs() != null && config.getProcessingGuarantees() != null
        && config.getProcessingGuarantees() != FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE) {
    throw new IllegalArgumentException("timeout requires ATLEAST_ONCE");
}

Type guard

boolean timeoutGuaranteeCompatible(FunctionConfig c) {
    return c.getTimeoutMs() == null || c.getProcessingGuarantees() == null
        || c.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE;
}

Try / catch

try {
    admin.functions().createFunction(functionConfig, sourceConfigLocation);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Message timeout")) {
        config.setTimeoutMs(null); // or switch guarantees, then retry
    } else { throw e; }
}

Prevention

When it happens

Trigger: createFunction/updateFunction with timeoutMs != null, processingGuarantees != null, and processingGuarantees != ATLEAST_ONCE — e.g. a function configured with --processing-guarantees EFFECTIVELY_ONCE that also carries a leftover --timeout-ms value.

Common situations: Switching a function from default (atleast_once) to effectively_once without clearing timeoutMs; merged configs from two sources each setting one of the fields; CLI scripts accumulating flags.

Understand the failure class

Related errors


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