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
- Remove/clear timeoutMs when using EFFECTIVELY_ONCE or ATMOST_ONCE
- Or set processingGuarantees back to ATLEAST_ONCE if timeout behavior is required
- 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
- Decide timeout vs guarantee mode together, not in separate config layers
- When switching to EFFECTIVELY_ONCE/ATMOST_ONCE, scrub timeoutMs first
- Add a config sanity test asserting the ATLEAST_ONCE + timeout pairing
- Avoid stacking CLI flags that set these independently
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Function timeout must be a positive number
- MaxMessageRetries and Effectively once don't gel well
- Either a Java jar or a Python file or a Go executable binary
- Either a Java jar or a Python file or a Go executable binary
- Function Name not provided
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/424e51f362656e41.
Report an issue: GitHub.