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
- Add .enableRetry(true) to the ConsumerBuilder before subscribing (requires a non-exclusive/shared subscription)
- Set a retry letter topic via deadLetterPolicy if you want a custom retry topic, otherwise the default <topic>-RETRY is used
- Recreate the consumer/subscription — retry enablement is fixed at subscription creation
- 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
- Call .enableRetry(true) on every builder whose consumers may use reconsumeLater
- Remember retry requires Shared/Key_Shared subscription type (not Exclusive/Failover)
- Consider negativeAcknowledge() as a simpler redelivery mechanism when retry topics are unnecessary
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
- Broker doesn't allow forced deletion of namespaces
- Local cluster is not part of replicate cluster list
- Entry filter '${filterName}' not found
- No entry filter is found for name `${filterName}`. Available
- Invalid auto split/merge configuration: ${message}
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/5db1481996bf0707.
Report an issue: GitHub.