apache/pulsar · warning · java.lang.UnsupportedOperationException

Expire message by timestamp is not supported for non-persist

Error message

Expire message by timestamp is not supported for non-persistent topic.

What it means

NonPersistentSubscription.expireMessages(int messageTTLInSeconds) unconditionally throws UnsupportedOperationException because message expiration by timestamp requires persistent storage/cursors that non-persistent topics do not have.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/nonpersistent/NonPersistentSubscription.java:498

            log.warn().attr("consumer", consumer).log("Error removing consumer");
            future.completeExceptionally(e);
        }
        return future;
    }

    @Override
    public List<Consumer> getConsumers() {
        Dispatcher dispatcher = this.dispatcher;
        if (dispatcher != null) {
            return dispatcher.getConsumers();
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    public boolean expireMessages(int messageTTLInSeconds) {
        throw new UnsupportedOperationException("Expire message by timestamp is not supported for"
                + " non-persistent topic.");
    }

    @Override
    public CompletableFuture<Boolean> expireMessagesAsync(int messageTTLInSeconds) {
        return CompletableFuture.failedFuture(new UnsupportedOperationException("Expire message by timestamp is not"
                + " supported for non-persistent topic."));
    }

    @Override
    public boolean expireMessages(Position position) {
        throw new UnsupportedOperationException("Expire message by position is not supported for"
                + " non-persistent topic.");
    }

    public NonPersistentSubscriptionStatsImpl getStats(GetStatsOptions getStatsOptions) {
        NonPersistentSubscriptionStatsImpl subStats = new NonPersistentSubscriptionStatsImpl();
        subStats.bytesOutCounter = bytesOutFromRemovedConsumers.longValue();

View on GitHub (pinned to 820761864e)

Solutions

  1. Use persistent topics if message TTL/expiry is required
  2. Avoid calling expireMessages on non-persistent subscriptions; rely on consumer-side message TTL checks instead
  3. Guard admin automation/scripts to skip non-persistent topics for TTL operations
  4. Catch UnsupportedOperationException and treat as a no-op for non-persistent topics

Example fix

// before
subscription.expireMessages(ttlSeconds);
// after
if (subscription.getTopic().isPersistentTopic()) {
    subscription.expireMessages(ttlSeconds);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!topicName.getPersistent()) {
    // skip TTL expiry for non-persistent topics
    return;
}
subscription.expireMessages(ttlSeconds);

Type guard

boolean supportsTtlExpiry(Topic t) {
    return t instanceof org.apache.pulsar.broker.service.persistent.PersistentTopic;
}

Try / catch

try {
    subscription.expireMessages(ttlSeconds);
} catch (UnsupportedOperationException e) {
    log.debug("TTL expiry unsupported for non-persistent topic");
}

Prevention

When it happens

Trigger: Calling subscription.expireMessages(messageTTLInSeconds) or admin/API paths that trigger TTL-based message expiry on a subscription of a non-persistent topic.

Common situations: Applying topic-level TTL policies (ttlDurationDefault / namespace TTL) to non-persistent topics; admin CLI/REST expire-messages call against a non-persistent topic; generic admin tooling that assumes all topics support expiry.

Related errors


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