apache/pulsar · error · java.lang.IllegalStateException

MessageIdV5 missing multi-topic vector — was the message…

Error message

MessageIdV5 missing multi-topic vector — was the message delivered through a multi-topic stream consumer?

What it means

Thrown by MultiTopicStreamConsumer.fanOutCumulativeAck when the MessageIdV5 lacks its multiTopicVector(). Without the per-topic id map, the consumer cannot route the cumulative ack across the topic set, so it fails with IllegalStateException.

Solutions

  1. Ack with ids delivered by this exact consumer instance
  2. Verify id.multiTopicVector() != null before calling acknowledgeCumulative
  3. When persisting ids, serialize the full multi-topic metadata

Example fix

// before
streamConsumer.acknowledgeCumulative(id); // vector-less id
// after
if (id instanceof MessageIdV5 v5 && v5.multiTopicVector() != null) {
    streamConsumer.acknowledgeCumulative(v5);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (id instanceof MessageIdV5 v5 && v5.multiTopicVector() == null) { handleVectorless(); }

Type guard

static boolean hasMultiTopicVector(MessageId id) { return id instanceof MessageIdV5 v5 && v5.multiTopicVector() != null; }

Try / catch

try { consumer.acknowledgeCumulative(id); } catch (IllegalStateException e) { log.warn("missing multi-topic vector", e); }

Prevention

When it happens

Trigger: Calling acknowledgeCumulative with a MessageIdV5 not produced by multi-topic stream delivery — single-topic v5 id, deserialized id missing vector metadata, or hand-constructed id.

Common situations: Persisting and later replaying ids without the multi-topic vector; acking across consumer instances; client version migration dropping metadata.

Related errors


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

Appendix: source

Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/MultiTopicStreamConsumer.java:334

    public void acknowledgeCumulative(MessageId messageId, Transaction txn) {
        // Transactions on multi-topic are best-effort across per-topic consumers — each
        // per-topic ack is independently transactional. See note in the design doc.
        fanOutCumulativeAck(messageId, (sc, vector) -> sc.ackUpToVector(vector));
    }

    /**
     * For a cumulative ack on a multi-topic message, look up its multi-topic vector
     * and invoke the per-topic ack on every parent topic.
     */
    private void fanOutCumulativeAck(MessageId messageId,
                                     java.util.function.BiConsumer<ScalableStreamConsumer<T>,
                                             Map<Long, org.apache.pulsar.client.api.MessageId>> action) {
        if (!(messageId instanceof MessageIdV5 id)) {
            throw new IllegalArgumentException("Expected MessageIdV5, got: " + messageId.getClass());
        }
        Map<String, Map<Long, org.apache.pulsar.client.api.MessageId>> vector = id.multiTopicVector();
        if (vector == null) {
            throw new IllegalStateException("MessageIdV5 missing multi-topic vector — was the"
                    + " message delivered through a multi-topic stream consumer?");
        }
        for (var entry : vector.entrySet()) {
            PerTopic<T> state = perTopic.get(entry.getKey());
            if (state == null) {
                // Topic left the matching set since this message was enqueued: we've
                // detached it and no longer ack removed topics, so skip its slice.
                continue;
            }
            action.accept(state.consumer, entry.getValue());
        }
    }

    @Override
    public AsyncStreamConsumer<T> async() {
        return asyncView;
    }

View on GitHub (pinned to 820761864e)