apache/pulsar · error · IllegalArgumentException
Tenants differ
Error message
Tenants differ
What it means
Update-compatibility check in SinkConfigUtils.validateUpdate: the existing sink and the new sink configuration belong to different tenants, and a sink update cannot move a sink across tenants (updates must keep tenant/namespace/name identity); the diverging tenant fields are the faulty input.
Source
Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java:613
retval.addAll(sinkConfig.getTopicToSchemaType().keySet());
}
if (sinkConfig.getInputSpecs() != null) {
retval.addAll(sinkConfig.getInputSpecs().keySet());
}
return retval;
}
@SneakyThrows
public static SinkConfig clone(SinkConfig sinkConfig) {
return ObjectMapperFactory.getMapper().reader().readValue(
ObjectMapperFactory.getMapper().writer().writeValueAsBytes(sinkConfig), SinkConfig.class);
}
public static SinkConfig validateUpdate(SinkConfig existingConfig, SinkConfig newConfig) {
SinkConfig mergedConfig = clone(existingConfig);
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("Sink Names differ");
}
if (!StringUtils.isEmpty(newConfig.getClassName())) {
mergedConfig.setClassName(newConfig.getClassName());
}
if (!StringUtils.isEmpty(newConfig.getSourceSubscriptionName()) && !newConfig.getSourceSubscriptionName()
.equals(existingConfig.getSourceSubscriptionName())) {
throw new IllegalArgumentException("Subscription Name cannot be altered");
}
if (newConfig.getInputSpecs() == null) {
newConfig.setInputSpecs(new HashMap<>());
}View on GitHub (pinned to 820761864e)
Solutions
- Keep the tenant unchanged; create a new sink under the other tenant
Example fix
// before
newConfig.setTenant("tenant-b");
admin.sinks().updateSink(existingTenant, ns, name, newConfig);
// after
newConfig.setTenant(existingTenant);
admin.sinks().updateSink(existingTenant, ns, name, newConfig); Defensive patterns
Strategy: validation
Validate before calling
if (!existing.getTenant().equals(newConfig.getTenant())) {
throw new IllegalStateException("Update cannot change tenant; delete+create instead");
} Try / catch
try {
admin.sinks().updateSink(tenant, ns, name, newConfig);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().equals("Tenants differ")) {
// create the sink under the new tenant instead
} else { throw e; }
} Prevention
- Build update configs from the existing config, overriding only mutable fields
- Never trust tenant/namespace fields in client-supplied update bodies
When it happens
Trigger: Calling SinkConfigUtils.validateUpdate(existingConfig, newConfig) where newConfig.getTenant() differs from existingConfig.getTenant().
Common situations: Reusing an update request payload across tenants; programmatically cloning a sink config and changing tenant to 'move' the sink instead of delete+create.
Related errors
- Namespaces differ
- Only one of serdeClassName or schemaType should be set
- When effectively once processing guarantee is specified, ret
- Only one of retain ordering or retain key ordering can be se
- %s Package is not provided
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/db8e4c6251ecb4a0.
Report an issue: GitHub.