apache/pulsar · warning · java.lang.UnsupportedOperationException

Expire message by position is not supported for non-persiste

Error message

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

What it means

NonPersistentSubscription.expireMessages(Position position) unconditionally throws UnsupportedOperationException — expiring messages up to a specific position (cumulative expiry) is not meaningful for non-persistent topics since messages are not retained in storage.

Source

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

            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();
        subStats.msgOutCounter = msgOutFromRemovedConsumer.longValue();

        NonPersistentDispatcher dispatcher = this.dispatcher;
        if (dispatcher != null) {
            dispatcher.getConsumers().forEach(consumer -> {
                ConsumerStatsImpl consumerStats = consumer.getStats();
                if (!getStatsOptions.isExcludeConsumers()) {
                    subStats.consumers.add(consumerStats);
                }
                subStats.msgRateOut += consumerStats.msgRateOut;
                subStats.messageAckRate += consumerStats.messageAckRate;
                subStats.msgThroughputOut += consumerStats.msgThroughputOut;

View on GitHub (pinned to 820761864e)

Solutions

  1. Restrict position-based expiry calls to persistent topics
  2. Check topic type via admin API (persistent topic namespace) before issuing expire-messages
  3. Handle the UnsupportedOperationException (or its async failed-future equivalent) in admin tooling
  4. Use disconnect-consumer or clear-backlog operations instead for non-persistent subscriptions

Example fix

// before
admin.topics().expireMessages(topic, subName, position);
// after
if (topic.startsWith("persistent://")) {
    admin.topics().expireMessages(topic, subName, position);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!topicName.getPersistent()) {
    return; // skip position-based expiry
}
admin.topics().expireMessages(topic, subName, position);

Type guard

boolean supportsPositionExpiry(String topic) {
    return topic.startsWith("persistent://");
}

Try / catch

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

Prevention

When it happens

Trigger: Calling subscription.expireMessages(Position) or admin REST/CLI 'expire messages up to position' against a subscription on a non-persistent topic.

Common situations: Admin console/API expire-messages-by-position invoked on non-persistent topic; automation scripts applying position-based expiry uniformly across topics.

Related errors


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