{"record":{"id":"096166bb560f2098","repo":"apache/pulsar","slug":"message-retries-not-yet-supported-in-python","errorCode":null,"errorMessage":"Message retries not yet supported in python","messagePattern":"Message retries not yet supported in python","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java","lineNumber":761,"sourceCode":"                            validatableFunctionPackage.getTypePool(), true);\n        }\n        return new FunctionConfigUtils.ExtractedFunctionDetails(\n                functionClassName,\n                typeArgs[0].asErasure().getTypeName(),\n                typeArgs[1].asErasure().getTypeName());\n    }\n\n    private static void doPythonChecks(FunctionConfig functionConfig) {\n        if (functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {\n            throw new RuntimeException(\"Effectively-once processing guarantees not yet supported in Python\");\n        }\n\n        if (functionConfig.getWindowConfig() != null) {\n            throw new IllegalArgumentException(\"There is currently no support windowing in python\");\n        }\n\n        if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0) {\n            throw new IllegalArgumentException(\"Message retries not yet supported in python\");\n        }\n    }\n\n    private static void doGolangChecks(FunctionConfig functionConfig) {\n        if (functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {\n            throw new RuntimeException(\"Effectively-once processing guarantees not yet supported in Go function\");\n        }\n\n        if (functionConfig.getWindowConfig() != null) {\n            throw new IllegalArgumentException(\"Windowing is not supported in Go function yet\");\n        }\n\n        if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0) {\n            throw new IllegalArgumentException(\"Message retries not yet supported in Go function\");\n        }\n    }\n\n    private static void verifyNoTopicClash(Collection<String> inputTopics, String outputTopic)","sourceCodeStart":743,"sourceCodeEnd":779,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java#L743-L779","documentation":"Pulsar Functions validates function configurations per runtime language. The Python runtime does not implement the message-retry feature (maxMessageRetries), so doPythonChecks rejects any Python function config where maxMessageRetries is set to a non-negative value. This is a deliberate guard against silently ignoring a retry setting that would have no effect.","triggerScenarios":"Creating or updating a Python function (pulsar-admin functions create/update, or FunctionConfigUtils.validateNonJavaFunction) with setMaxMessageRetries(N) where N != null and N >= 0, or equivalently passing --max-message-retries on the CLI for a --py function.","commonSituations":"Copy-pasting a Java function config to a Python function; enabling retries to handle processing failures; templates or CI pipelines that set maxMessageRetries uniformly for all functions regardless of runtime.","solutions":["Remove maxMessageRetries (or set it to null / leave --max-message-retries unset) from the Python function config","Handle retries inside the Python function logic itself (catch exceptions, track attempts, re-publish to a retry topic)","Migrate the function to the Java runtime if message retries are a hard requirement"],"exampleFix":"// before\nFunctionConfig config = new FunctionConfig();\nconfig.setRuntime(FunctionConfig.Runtime.PYTHON);\nconfig.setMaxMessageRetries(3);\n\n// after\nFunctionConfig config = new FunctionConfig();\nconfig.setRuntime(FunctionConfig.Runtime.PYTHON);\nconfig.setMaxMessageRetries(null); // retries unsupported in Python runtime","handlingStrategy":"validation","validationCode":"if (config.getRuntime() == FunctionConfig.Runtime.PYTHON\n        && config.getMaxMessageRetries() != null\n        && config.getMaxMessageRetries() >= 0) {\n    throw new IllegalArgumentException(\"maxMessageRetries is unsupported for Python functions; remove it\");\n}\nFunctionConfigUtils.validateFunctionConfig(config, null); // only after the guard passes","typeGuard":"boolean pythonRetriesAllowed(FunctionConfig c) {\n    return c.getRuntime() != FunctionConfig.Runtime.PYTHON\n        || c.getMaxMessageRetries() == null\n        || c.getMaxMessageRetries() < 0;\n}","tryCatchPattern":"try {\n    FunctionConfigUtils.validateFunctionConfig(config, null);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Message retries not yet supported\")) {\n        config.setMaxMessageRetries(null); // drop unsupported option for Python runtime\n    } else {\n        throw e;\n    }\n}","preventionTips":["Never set maxMessageRetries in configs used for Python or Go functions","Keep per-runtime config templates instead of a shared config for Java/Python/Go functions","Validate configs in CI before deployment with FunctionConfigUtils or a dry-run create","Check runtime feature-parity tables before copying Java function options to Python/Go"],"tags":["pulsar-functions","python-runtime","unsupported-feature","configuration"],"backgroundTag":"unsupported-feature-for-runtime","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"}