apache/pulsar · error · java.lang.IllegalStateException
MessageIdV5 missing parent topic — was the message delivered
Error message
MessageIdV5 missing parent topic — was the message delivered through a multi-topic consumer?
What it means
Thrown by MultiTopicQueueConsumer.routeAck when the MessageIdV5 is valid but its parentTopic() is null. A multi-topic ack needs to know which underlying topic the message came from; a null parent topic means the id was not produced by this multi-topic consumer's delivery path.
Source
Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/MultiTopicQueueConsumer.java:335
@Override
public void acknowledge(MessageId messageId, Transaction txn) {
routeAck(messageId, ptc -> ptc.acknowledge(messageId, txn));
}
@Override
public void negativeAcknowledge(MessageId messageId) {
routeAck(messageId, ptc -> ptc.negativeAcknowledge(messageId));
}
/** Look up the per-topic consumer via the parent topic tag and delegate. */
private void routeAck(MessageId messageId, java.util.function.Consumer<QueueConsumer<T>> action) {
if (!(messageId instanceof MessageIdV5 id)) {
throw new IllegalArgumentException("Expected MessageIdV5, got: " + messageId.getClass());
}
String parent = id.parentTopic();
if (parent == null) {
throw new IllegalStateException("MessageIdV5 missing parent topic — was the message"
+ " delivered through a multi-topic consumer?");
}
PerTopicState<T> state = perTopic.get(parent);
if (state == null) {
// Topic was removed between deliver and ack. Fine — broker has dropped the
// session for that topic. Drop the ack silently.
log.debug().attr("topic", parent)
.log("Ack for removed topic; dropping");
return;
}
action.accept(state.consumer);
}
@Override
public AsyncQueueConsumer<T> async() {
return asyncView;
}
View on GitHub (pinned to 820761864e)
Solutions
- Ack messages with the same consumer instance that delivered them
- Verify the MessageIdV5 was created by multi-topic delivery (parentTopic() != null) before acking
- If persisting ids, store the parent topic alongside and reconstruct a valid id
Example fix
// before
otherConsumer.acknowledge(messageId); // id has no parent topic
// after
if (messageId instanceof MessageIdV5 id && id.parentTopic() != null) {
multiTopicConsumer.acknowledge(id);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (id instanceof MessageIdV5 v5 && v5.parentTopic() == null) { handleOrphan(); } Type guard
static boolean hasParentTopic(MessageId id) { return id instanceof MessageIdV5 v5 && v5.parentTopic() != null; } Try / catch
try { consumer.acknowledge(id); } catch (IllegalStateException e) { log.warn("id not from multi-topic delivery", e); } Prevention
- Ack with the delivering consumer only
- Persist parent-topic metadata alongside stored ids
When it happens
Trigger: Calling acknowledge/negativeAcknowledge with a MessageIdV5 that was created outside multi-topic delivery — e.g. from a single-topic MessageIdV5 consumer, manually constructed, or deserialized without topic metadata.
Common situations: Ids shared across consumer instances; ids deserialized from a database/journal where parent-topic metadata was stripped; acking a message received by a different consumer.
Related errors
- MessageIdV5 missing multi-topic vector — was the message del
- Expected MessageIdV5, got: ${messageId.getClass()}
- Expected MessageIdV5, got: ${messageId.getClass()}
- Try to reserve/release memory failed, the param memorySize i
- Failed to decode message from topic ${topic} with schemaId $
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/1a3b5d2131798716.
Report an issue: GitHub.