{"record":{"id":"424e51f362656e41","repo":"apache/pulsar","slug":"message-timeout-can-only-be-specified-with-process","errorCode":null,"errorMessage":"Message timeout can only be specified with processing guarantee is ATLEAST_ONCE","messagePattern":"Message timeout can only be specified with processing guarantee is ATLEAST_ONCE","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":866,"sourceCode":"            Boolean windowAutoAck = functionConfig.getAutoAck();\n            if (windowAutoAck != null && windowAutoAck) {\n                throw new IllegalArgumentException(\"Cannot enable auto ack when using windowing functionality\");\n            }\n            WindowConfigUtils.validate(windowConfig);\n        }\n\n        if (functionConfig.getResources() != null) {\n            ResourceConfigUtils.validate(functionConfig.getResources());\n        }\n\n        if (functionConfig.getTimeoutMs() != null && functionConfig.getTimeoutMs() <= 0) {\n            throw new IllegalArgumentException(\"Function timeout must be a positive number\");\n        }\n\n        if (functionConfig.getTimeoutMs() != null\n                && functionConfig.getProcessingGuarantees() != null\n                && functionConfig.getProcessingGuarantees() != FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE) {\n            throw new IllegalArgumentException(\"Message timeout can only be specified with processing guarantee is \"\n                    + FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE.name());\n        }\n\n        if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0\n                && functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {\n            throw new IllegalArgumentException(\"MaxMessageRetries and Effectively once don't gel well\");\n        }\n        if ((functionConfig.getMaxMessageRetries() == null || functionConfig.getMaxMessageRetries() < 0)\n                && !org.apache.commons.lang3.StringUtils.isEmpty(functionConfig.getDeadLetterTopic())) {\n            throw new IllegalArgumentException(\"Dead Letter Topic specified, however max retries is set to infinity\");\n        }\n        if (functionConfig.getRetainKeyOrdering() != null\n                && functionConfig.getRetainKeyOrdering()\n                && functionConfig.getProcessingGuarantees() != null\n                && functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {\n            throw new IllegalArgumentException(\n                    \"When effectively once processing guarantee is specified, retain Key ordering cannot be set\");\n        }","sourceCodeStart":848,"sourceCodeEnd":884,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L848-L884","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconfig.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE);\nconfig.setTimeoutMs(30000L);\n// after\nconfig.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE);\n// timeout removed\nconfig.setTimeoutMs(null);","handlingStrategy":"validation","validationCode":"if (config.getTimeoutMs() != null && config.getProcessingGuarantees() != null\n        && config.getProcessingGuarantees() != FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE) {\n    throw new IllegalArgumentException(\"timeout requires ATLEAST_ONCE\");\n}","typeGuard":"boolean timeoutGuaranteeCompatible(FunctionConfig c) {\n    return c.getTimeoutMs() == null || c.getProcessingGuarantees() == null\n        || c.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE;\n}","tryCatchPattern":"try {\n    admin.functions().createFunction(functionConfig, sourceConfigLocation);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Message timeout\")) {\n        config.setTimeoutMs(null); // or switch guarantees, then retry\n    } else { throw e; }\n}","preventionTips":["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"],"tags":["pulsar-functions","validation","timeout","processing-guarantees"],"backgroundTag":"incompatible-function-options","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}