{"record":{"id":"3617cad9043970ab","repo":"apache/pulsar","slug":"tenants-differ-3617ca","errorCode":null,"errorMessage":"Tenants differ","messagePattern":"Tenants differ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java","lineNumber":383,"sourceCode":"                validateSourceConfig(sourceConfig, sourceFunction);\n            } else {\n                log.warn(\"Skipping annotation based validation of sink config as classloading is disabled\");\n            }\n        }\n\n        return new ExtractedSourceDetails(sourceClassName, typeArg.asErasure().getTypeName());\n    }\n\n    @SneakyThrows\n    public static SourceConfig clone(SourceConfig sourceConfig) {\n        return ObjectMapperFactory.getMapper().reader().readValue(\n                ObjectMapperFactory.getMapper().writer().writeValueAsBytes(sourceConfig), SourceConfig.class);\n    }\n\n    public static SourceConfig validateUpdate(SourceConfig existingConfig, SourceConfig newConfig) {\n        SourceConfig mergedConfig = clone(existingConfig);\n        if (!existingConfig.getTenant().equals(newConfig.getTenant())) {\n            throw new IllegalArgumentException(\"Tenants differ\");\n        }\n        if (!existingConfig.getNamespace().equals(newConfig.getNamespace())) {\n            throw new IllegalArgumentException(\"Namespaces differ\");\n        }\n        if (!existingConfig.getName().equals(newConfig.getName())) {\n            throw new IllegalArgumentException(\"Function Names differ\");\n        }\n        if (!StringUtils.isEmpty(newConfig.getClassName())) {\n            mergedConfig.setClassName(newConfig.getClassName());\n        }\n        if (!StringUtils.isEmpty(newConfig.getTopicName())) {\n            mergedConfig.setTopicName(newConfig.getTopicName());\n        }\n        if (!StringUtils.isEmpty(newConfig.getSerdeClassName())) {\n            mergedConfig.setSerdeClassName(newConfig.getSerdeClassName());\n        }\n        if (!StringUtils.isEmpty(newConfig.getSchemaType())) {\n            mergedConfig.setSchemaType(newConfig.getSchemaType());","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SourceConfigUtils.java#L365-L401","documentation":"SourceConfigUtils.validateUpdate enforces that a source update cannot change the tenant that owns the source. The existing stored config and the new config must have identical tenant values, otherwise the update is rejected.","triggerScenarios":"Calling validateUpdate(existingConfig, newConfig) where existingConfig.getTenant() does not equal newConfig.getTenant() — typically via a source update API where the submitted config names a different tenant.","commonSituations":"Admin copying a source config into another tenant and submitting it as an update instead of creating a new source; typo in tenant name in the update request; tooling that regenerates the full config with defaults pointing to a different tenant.","solutions":["Resubmit the update with the tenant matching the existing source's tenant","If the source genuinely must move tenants, delete it and create a new source under the target tenant","Fix tooling/templates so they preserve the original tenant field on update"],"exampleFix":"// before\nSourceConfig update = config; update.setTenant(\"tenantB\");\n// after\nSourceConfig update = config; update.setTenant(existingConfig.getTenant()); // create a new source instead to move tenants","handlingStrategy":"validation","validationCode":"if (!Objects.equals(existing.getTenant(), update.getTenant())) {\n    throw new IllegalArgumentException(\"tenant is immutable on update\");\n}","typeGuard":null,"tryCatchPattern":"try { SourceConfigUtils.validateUpdate(existing, update); } catch (IllegalArgumentException e) { if (e.getMessage().equals(\"Tenants differ\")) { update.setTenant(existing.getTenant()); } else { throw e; } }","preventionTips":["Build update payloads from the existing config (clone + mutate) instead of from templates","Never edit tenant/namespace/name fields in update requests","To move a source, delete and recreate under the target tenant","Compare submitted config against stored config in CI before issuing updates"],"tags":["pulsar-functions","immutable-field","update-validation"],"backgroundTag":"immutable-field-change","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}