apache/pulsar · error · IllegalArgumentException
Tenants differ
Error message
Tenants differ
What it means
validateUpdate merges a new function config into an existing one, but the function's identity (tenant, namespace, name) is immutable. This error is thrown when the new config's tenant does not match the existing config's tenant, i.e. an update attempts to move the function to a different tenant.
Source
Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/FunctionConfigUtils.java:988
if (functionConfig.getRuntime() == FunctionConfig.Runtime.GO) {
doGolangChecks(functionConfig);
} else if (functionConfig.getRuntime() == FunctionConfig.Runtime.PYTHON) {
doPythonChecks(functionConfig);
} else {
throw new IllegalArgumentException("Function language runtime is either not set or cannot be determined");
}
}
public static ExtractedFunctionDetails validateJavaFunction(FunctionConfig functionConfig,
ValidatableFunctionPackage validatableFunctionPackage) {
doCommonChecks(functionConfig);
return doJavaChecks(functionConfig, validatableFunctionPackage);
}
public static FunctionConfig validateUpdate(FunctionConfig existingConfig, FunctionConfig newConfig) {
FunctionConfig mergedConfig = existingConfig.toBuilder().build();
if (!existingConfig.getTenant().equals(newConfig.getTenant())) {
throw new IllegalArgumentException("Tenants differ");
}
if (!existingConfig.getNamespace().equals(newConfig.getNamespace())) {
throw new IllegalArgumentException("Namespaces differ");
}
if (!existingConfig.getName().equals(newConfig.getName())) {
throw new IllegalArgumentException("Function Names differ");
}
if (!StringUtils.isEmpty(newConfig.getClassName())) {
mergedConfig.setClassName(newConfig.getClassName());
}
if (!StringUtils.isEmpty(newConfig.getJar())) {
mergedConfig.setJar(newConfig.getJar());
}
if (newConfig.getInputSpecs() == null) {
newConfig.setInputSpecs(new HashMap<>());
}View on GitHub (pinned to 820761864e)
Solutions
- Keep the tenant identical to the existing function in the update payload.
- If a different tenant is genuinely needed, delete the function and create a new one under the target tenant.
- Verify the tenant name spelling/case matches the existing registration exactly.
Example fix
// before
updateConfig.setTenant("public-other"); // differs from existing
// after
updateConfig.setTenant(existingConfig.getTenant()); // e.g. "public" Defensive patterns
Strategy: validation
Validate before calling
if (!existingConfig.getTenant().equals(newConfig.getTenant())) {
throw new IllegalArgumentException("Update payload must reuse tenant " + existingConfig.getTenant());
} Type guard
boolean sameIdentity(FunctionConfig a, FunctionConfig b) {
return a.getTenant().equals(b.getTenant())
&& a.getNamespace().equals(b.getNamespace())
&& a.getName().equals(b.getName());
} Try / catch
try {
merged = FunctionConfigUtils.validateUpdate(existing, updated);
} catch (IllegalArgumentException e) {
if (e.getMessage().equals("Tenants differ")) {
updated.setTenant(existing.getTenant());
merged = FunctionConfigUtils.validateUpdate(existing, updated);
} else { throw e; }
} Prevention
- Load the existing config via the admin API and copy its tenant/namespace/name into the update payload.
- Never edit identity fields in deploy templates.
- Recreate (delete + create) when a tenant move is actually intended.
When it happens
Trigger: Calling the update function admin API (PUT on an existing function) with a FunctionConfig whose getTenant() differs from the currently registered function's tenant.
Common situations: Reusing a config file meant for a different tenant/environment; templated deployments where a tenant placeholder changed between create and update; copy-paste between tenants.
Related errors
- Namespaces differ
- Function Names differ
- Input Topics cannot be altered
- isRegexPattern for input topic
- Output Serde mismatch
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/911ad40a02eb49ba.
Report an issue: GitHub.