apache/pulsar · error · PulsarClientException

reconsumeLater method not supported because retryEnabled is

Error message

reconsumeLater method not supported because retryEnabled is set to false. You can enable it via ConsumerBuilder.

What it means

reconsumeLater() delivers a message back to the retry-letter topic for redelivery with optional delay; this only works when the subscription was created with retryEnabled (dead-letter/retry machinery on the subscription). ConsumerBase.reconsumeLater checks conf.isRetryEnable() and throws a PulsarClientException with RECONSUME_LATER_ERROR_MSG when retry is disabled, because without the retry topic there is nowhere to re-publish the message.

Source

Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/ConsumerBase.java:509

            acknowledgeAsync(messages).get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw PulsarClientException.unwrap(e);
        } catch (ExecutionException e) {
            throw PulsarClientException.unwrap(e);
        }
    }

    @Override
    public void reconsumeLater(Message<?> message, long delayTime, TimeUnit unit) throws PulsarClientException {
        reconsumeLater(message, null, delayTime, unit);
    }

    @Override
    public void reconsumeLater(Message<?> message, Map<String, String> customProperties, long delayTime, TimeUnit unit)
            throws PulsarClientException {
        if (!conf.isRetryEnable()) {
            throw new PulsarClientException(RECONSUME_LATER_ERROR_MSG);
        }
        try {
            reconsumeLaterAsync(message, customProperties, delayTime, unit).get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw PulsarClientException.unwrap(e);
        } catch (ExecutionException e) {
            throw PulsarClientException.unwrap(e);
        }
    }

    @Override
    public void reconsumeLater(Messages<?> messages, long delayTime, TimeUnit unit) throws PulsarClientException {
        try {
            reconsumeLaterAsync(messages, delayTime, unit).get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw PulsarClientException.unwrap(e);

View on GitHub (pinned to 820761864e)

Solutions

  1. Add .enableRetry(true) to the ConsumerBuilder before subscribing (requires a non-exclusive/shared subscription)
  2. Set a retry letter topic via deadLetterPolicy if you want a custom retry topic, otherwise the default <topic>-RETRY is used
  3. Recreate the consumer/subscription — retry enablement is fixed at subscription creation
  4. Use negativeAcknowledge() or acknowledge with dead-letter policy as an alternative for redelivery

Example fix

// before
Consumer<String> c = client.newConsumer(Schema.STRING)
    .topic("t").subscriptionName("s")
    .subscriptionType(SubscriptionType.Shared)
    .subscribe();
c.reconsumeLater(msg, 10, TimeUnit.SECONDS); // throws
// after
Consumer<String> c = client.newConsumer(Schema.STRING)
    .topic("t").subscriptionName("s")
    .subscriptionType(SubscriptionType.Shared)
    .enableRetry(true)
    .subscribe();
c.reconsumeLater(msg, 10, TimeUnit.SECONDS);
Defensive patterns

Strategy: validation

Validate before calling

// at consumer creation:
Consumer<T> c = client.newConsumer(schema)
    .topic(topic).subscriptionName(sub)
    .subscriptionType(SubscriptionType.Shared)
    .enableRetry(true)
    .subscribe();
// keep retryEnabled flag alongside the consumer reference
if (!retryEnabled) {
    throw new IllegalStateException("reconsumeLater requires enableRetry(true)");
}

Type guard

boolean supportsReconsumeLater(org.apache.pulsar.client.api.Consumer<T> c) {
    return ((ConsumerBase<T>) c).getConf().isRetryEnable();
}

Try / catch

try {
    consumer.reconsumeLater(msg, 10, TimeUnit.SECONDS);
} catch (PulsarClientException e) {
    if (String.valueOf(e.getMessage()).contains("retryEnabled")) {
        consumer.negativeAcknowledge(msg); // fallback redelivery
    }
}

Prevention

When it happens

Trigger: Calling consumer.reconsumeLater(message, customProperties, delayTime, unit) (or the 2-arg overload) on a consumer whose ConsumerBuilder did not call enableRetry(true). The async variant reconsumeLaterAsync fails with the same message.

Common situations: Adding retry semantics to an existing subscription after the fact without recreating it with enableRetry; assuming reconsumeLater works on any subscription type; forgetting that retry requires a shared subscription and a retryLetterTopic configuration for full function.

Related errors


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