apache/pulsar · error · RestException

Invalid value for message TTL

Error message

Invalid value for message TTL

What it means

Thrown while updating namespace policies (setPolicies / setNamespaceMessageTTL path): after validating clusters, the broker checks that message_ttl_in_seconds is not negative. A negative message TTL is meaningless (messages would expire before arrival), so the update is rejected with HTTP 412 PRECONDITION_FAILED.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/NamespacesBase.java:2418

    }

    private CompletableFuture<Void> validatePoliciesAsync(NamespaceName ns, Policies policies) {
        if (!policies.checkAllowedAndReplicationClusters()) {
            String msg = String.format("[%s] All replication clusters should be included in allowed clusters."
                    + " Repl clusters: %s, allowed clusters: %s",
                    ns.toString(), policies.replication_clusters, policies.allowed_clusters);
            log.info(msg);
            return FutureUtil.failedFuture(new RestException(Status.BAD_REQUEST, msg));
        }
        pulsar().getBrokerService().setCurrentClusterAllowedWhenCreating(ns, policies);

        // Validate cluster names and permissions
        return Stream.concat(policies.replication_clusters.stream(), policies.allowed_clusters.stream())
                    .map(cluster -> validateClusterForTenantAsync(ns.getTenant(), cluster))
                    .reduce(CompletableFuture.completedFuture(null), (a, b) -> a.thenCompose(ignore -> b))
            .thenAccept(__ -> {
                if (policies.message_ttl_in_seconds != null && policies.message_ttl_in_seconds < 0) {
                    throw new RestException(Status.PRECONDITION_FAILED, "Invalid value for message TTL");
                }

                if (policies.bundles != null && policies.bundles.getNumBundles() > 0) {
                    if (policies.bundles.getBoundaries() == null || policies.bundles.getBoundaries().size() == 0) {
                        policies.bundles = getBundles(policies.bundles.getNumBundles());
                    } else {
                        policies.bundles = validateBundlesData(policies.bundles);
                    }
                } else {
                    int defaultNumberOfBundles = config().getDefaultNumberOfNamespaceBundles();
                    policies.bundles = getBundles(defaultNumberOfBundles);
                }

                if (policies.persistence != null) {
                    validatePersistencePolicies(policies.persistence);
                }

                if (policies.retention_policies != null) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Set messageTtlInSeconds to 0 (no TTL) or a positive number of seconds
  2. To unset/clear the TTL, pass null (omit the field) rather than -1
  3. Fix the generating script/config so it clamps negative values to null or 0
  4. Re-send the policies update with the corrected value

Example fix

// before
Policies p = policies();
p.message_ttl_in_seconds = -1; // 412 Invalid value for message TTL
admin.namespaces().setPolicies("my-tenant/my-ns", p);
// after
p.message_ttl_in_seconds = null; // unset, or 0/positive value
admin.namespaces().setPolicies("my-tenant/my-ns", p);
Defensive patterns

Strategy: validation

Validate before calling

if (policies.message_ttl_in_seconds != null && policies.message_ttl_in_seconds < 0) {
    policies.message_ttl_in_seconds = null; // or 0
}

Type guard

boolean isValidTtl(Integer ttl) { return ttl == null || ttl >= 0; }

Try / catch

try {
    admin.namespaces().setPolicies(ns, policies);
} catch (PulsarAdminException.PreconditionFailedException e) {
    log.error("Invalid negative TTL {}; use null to unset or 0 to disable", policies.message_ttl_in_seconds);
    throw e;
}

Prevention

When it happens

Trigger: PUT /namespaces/{ns} (policies update) with messageTtlInSeconds < 0; passing -1 intending 'unset' when the correct way to unset is null; automation computing TTL from a formula that produced a negative value; client SDKs serializing an unset field as -1.

Common situations: Using -1 to 'disable' TTL (must use null/omit instead); misconfigured YAML/JSON where a variable was empty and coerced to -1; template rendering bugs producing negative values; older scripts written for systems that accepted -1 as unset.

Related errors


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