apache/pulsar · warning · RestException

Update contains no change

Error message

Update contains no change

What it means

updateSource rejects a request that would produce no effect: the merged SourceConfig equals the existing one, no package URL and no uploaded package file are supplied, and updateOptions either is null or does not request auth-data update. The worker short-circuits with 400 BAD_REQUEST instead of performing a pointless metadata update.

Source

Thrown at pulsar-functions/worker/src/main/java/org/apache/pulsar/functions/worker/rest/api/SourcesImpl.java:324

        SourceConfig existingSourceConfig =
                SourceConfigUtils.convertFromDetails(existingComponent.getFunctionDetails());
        // The rest end points take precedence over whatever is there in functionconfig
        sourceConfig.setTenant(tenant);
        sourceConfig.setNamespace(namespace);
        sourceConfig.setName(sourceName);
        SourceConfig mergedConfig;
        try {
            mergedConfig = SourceConfigUtils.validateUpdate(existingSourceConfig, sourceConfig);
        } catch (Exception e) {
            throw new RestException(Response.Status.BAD_REQUEST, e.getMessage());
        }

        if (existingSourceConfig.equals(mergedConfig) && isBlank(sourcePkgUrl) && uploadedInputStream == null
            && (updateOptions == null || !updateOptions.isUpdateAuthData())) {
            log.error().attr("tenant", tenant).attr("namespace", namespace).attr("componentName", sourceName)

                    .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,
                        sourcePkgUrl,
                        existingComponent.getPackageLocation().getPackagePath(),
                        uploadedInputStream);
                functionDetails = validateUpdateRequestParams(tenant, namespace, sourceName,
                        mergedConfig, componentPackageFile);
                if (existingComponent.getPackageLocation().getPackagePath().startsWith(Utils.BUILTIN)
                        && !isFunctionCodeBuiltin(functionDetails)
                        && (componentPackageFile == null || fileDetail == null)) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Change something real: modify a mutable field in the SourceConfig (e.g. configs, parallelism, processing guarantees)
  2. Provide a new package via sourcePkgUrl or upload the package in the request
  3. If the update is only about secrets/auth data, pass UpdateOptions with updateAuthData=true
  4. If the no-op is intentional, skip the API call — compare the existing config to the desired one before invoking update

Example fix

// before
admin.sources().updateSource(tenant, ns, name, sameConfig);
// after
UpdateOptions opts = new UpdateOptions();
opts.setUpdateAuthData(true); // if only refreshing auth data
admin.sources().updateSource(tenant, ns, name, sameConfig, opts);
// or skip entirely:
if (!existing.equals(desired)) { admin.sources().updateSource(...); }
Defensive patterns

Strategy: validation

Validate before calling

SourceConfig existing = admin.sources().getSourceConfig(tenant, ns, name);
SourceConfig merged = SourceConfigUtils.validateUpdate(existing, desired);
boolean authData = opts != null && opts.isUpdateAuthData();
if (existing.equals(merged) && pkgUrl == null && uploadStream == null && !authData) {
    log.info("Skipping no-op source update for {}", name);
} else {
    admin.sources().updateSource(tenant, ns, name, desired, opts);
}

Try / catch

try {
    admin.sources().updateSource(tenant, ns, name, cfg);
} catch (PulsarAdminException e) {
    if (e.getMessage() != null && e.getMessage().contains("no change")) {
        log.info("Source {} already up to date", name); // treat as success
    } else { throw e; }
}

Prevention

When it happens

Trigger: PUT /admin/v3/sources/{tenant}/{namespace}/{sourceName} where the submitted config is byte-for-byte equivalent to the stored config after merging, isBlank(sourcePkgUrl), uploadedInputStream == null, and updateOptions == null || !updateOptions.isUpdateAuthData().

Common situations: Re-running an automation/deployment script that applies a desired state already applied; resubmitting the same config fetched from GET; a CI job that always PUTs the source config even when nothing changed.

Related errors


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