apache/pulsar · warning · RestException
Update contains no change
Error message
Update contains no change
What it means
After merging the submitted SinkConfig with the existing one, updateSink rejects the request with HTTP 400 "Update contains no change" when the merged config equals the existing config AND no new package URL, no uploaded package file, and no UpdateOptions.isUpdateAuthData flag were supplied. The worker refuses no-op updates because they would needlessly bump metadata versions.
Source
Thrown at pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/rest/api/SinksImpl.java:331
SinkConfig existingSinkConfig = SinkConfigUtils.convertFromDetails(existingComponent.getFunctionDetails());
// The rest end points take precedence over whatever is there in functionconfig
sinkConfig.setTenant(tenant);
sinkConfig.setNamespace(namespace);
sinkConfig.setName(sinkName);
SinkConfig mergedConfig;
try {
mergedConfig = SinkConfigUtils.validateUpdate(existingSinkConfig, sinkConfig);
} catch (Exception e) {
throw new RestException(Response.Status.BAD_REQUEST, e.getMessage());
}
if (existingSinkConfig.equals(mergedConfig) && isBlank(sinkPkgUrl) && uploadedInputStream == null
&& (updateOptions == null || !updateOptions.isUpdateAuthData())) {
log.error().attr("tenant", tenant).attr("namespace", namespace).attr("componentName", sinkName)
.log("/ / Update contains no changes");
throw new RestException(Response.Status.BAD_REQUEST, "Update contains no change");
}
FunctionDetails functionDetails;
File componentPackageFile = null;
try {
// validate parameters
try {
componentPackageFile = getPackageFile(
componentType,
sinkPkgUrl,
existingComponent.getPackageLocation().getPackagePath(),
uploadedInputStream);
functionDetails = validateUpdateRequestParams(tenant, namespace, sinkName,
mergedConfig, componentPackageFile);
if (existingComponent.getPackageLocation().getPackagePath().startsWith(Utils.BUILTIN)
&& !isFunctionCodeBuiltin(functionDetails)
&& (componentPackageFile == null || fileDetail == null)) {View on GitHub (pinned to 820761864e)
Solutions
- Verify the update actually changes something; skip the call if the config is unchanged.
- Pass UpdateOptions with updateAuthData=true if the intent is to refresh auth data.
- Provide a new packageUrl or upload a new archive if the code changed.
- Diff your payload against GET /admin/v3/sinks/{tenant}/{namespace}/{name} before submitting.
Example fix
// before: PUT with unchanged config -> 400 admin.sinks().updateSink(tenant, ns, name, sameConfig, null, null); // after: signal an intentional no-config update UpdateOptions opts = UpdateOptions.builder().updateAuthData(true).build(); admin.sinks().updateSink(tenant, ns, name, sameConfig, null, null, opts);
Defensive patterns
Strategy: validation
Validate before calling
SinkConfig current = admin.sinks().getSinkConfig(tenant, ns, name);
if (current.equals(newConfig)) {
return; // skip no-op update
} Try / catch
try {
admin.sinks().updateSink(tenant, ns, name, cfg, pkgUrl, null);
} catch (PulsarAdminException e) {
if ("Update contains no change".equals(e.getMessage())) {
log.info("No-op update skipped");
}
} Prevention
- Diff payloads against the live config in deployment scripts.
- Make pipelines idempotent by skipping identical configs.
- Pass UpdateOptions.updateAuthData(true) when refreshing credentials intentionally.
When it happens
Trigger: PUT /admin/v3/sinks/{tenant}/{namespace}/{name} with a body identical to the current sink config, no sinkPkgUrl/uploadedInputStream, and updateOptions null or updateAuthData=false.
Common situations: Idempotent deployment scripts re-applying the same config; accidentally posting the config fetched from GET unchanged; retry after a failed update that had already taken effect.
Related errors
- <validation message from IllegalArgumentException>
- %s %s doesn't have instance with id %s
- Function in trigger function has more than 1 input topics
- Function in trigger function has unidentified topic
- %s Package is not provided
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/d05b5b63f5d0dd7f.
Report an issue: GitHub.