apache/pulsar · error · IllegalArgumentException

Message retries not yet supported in python

Error message

Message retries not yet supported in python

What it means

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.

Source

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

                            validatableFunctionPackage.getTypePool(), true);
        }
        return new FunctionConfigUtils.ExtractedFunctionDetails(
                functionClassName,
                typeArgs[0].asErasure().getTypeName(),
                typeArgs[1].asErasure().getTypeName());
    }

    private static void doPythonChecks(FunctionConfig functionConfig) {
        if (functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {
            throw new RuntimeException("Effectively-once processing guarantees not yet supported in Python");
        }

        if (functionConfig.getWindowConfig() != null) {
            throw new IllegalArgumentException("There is currently no support windowing in python");
        }

        if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0) {
            throw new IllegalArgumentException("Message retries not yet supported in python");
        }
    }

    private static void doGolangChecks(FunctionConfig functionConfig) {
        if (functionConfig.getProcessingGuarantees() == FunctionConfig.ProcessingGuarantees.EFFECTIVELY_ONCE) {
            throw new RuntimeException("Effectively-once processing guarantees not yet supported in Go function");
        }

        if (functionConfig.getWindowConfig() != null) {
            throw new IllegalArgumentException("Windowing is not supported in Go function yet");
        }

        if (functionConfig.getMaxMessageRetries() != null && functionConfig.getMaxMessageRetries() >= 0) {
            throw new IllegalArgumentException("Message retries not yet supported in Go function");
        }
    }

    private static void verifyNoTopicClash(Collection<String> inputTopics, String outputTopic)

View on GitHub (pinned to 820761864e)

Solutions

  1. Remove maxMessageRetries (or set it to null / leave --max-message-retries unset) from the Python function config
  2. Handle retries inside the Python function logic itself (catch exceptions, track attempts, re-publish to a retry topic)
  3. Migrate the function to the Java runtime if message retries are a hard requirement

Example fix

// before
FunctionConfig config = new FunctionConfig();
config.setRuntime(FunctionConfig.Runtime.PYTHON);
config.setMaxMessageRetries(3);

// after
FunctionConfig config = new FunctionConfig();
config.setRuntime(FunctionConfig.Runtime.PYTHON);
config.setMaxMessageRetries(null); // retries unsupported in Python runtime
Defensive patterns

Strategy: validation

Validate before calling

if (config.getRuntime() == FunctionConfig.Runtime.PYTHON
        && config.getMaxMessageRetries() != null
        && config.getMaxMessageRetries() >= 0) {
    throw new IllegalArgumentException("maxMessageRetries is unsupported for Python functions; remove it");
}
FunctionConfigUtils.validateFunctionConfig(config, null); // only after the guard passes

Type guard

boolean pythonRetriesAllowed(FunctionConfig c) {
    return c.getRuntime() != FunctionConfig.Runtime.PYTHON
        || c.getMaxMessageRetries() == null
        || c.getMaxMessageRetries() < 0;
}

Try / catch

try {
    FunctionConfigUtils.validateFunctionConfig(config, null);
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("Message retries not yet supported")) {
        config.setMaxMessageRetries(null); // drop unsupported option for Python runtime
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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