Pulsar’s acknowledgment mechanism, often assumed to be a simple "message received" flag, is actually a sophisticated dance between producer, broker, and consumer, with two distinct, and often misunderstood, modes: cumulative and individual.

Let’s see this in action. Imagine a consumer reading a few messages from a topic.

# Consumer starts reading, gets message_id_1, message_id_2, message_id_3

# Consumer processes message_id_1 successfully
pulsar-client consume my_topic -s subscription_name --ack-message message_id_1

If this were cumulative acknowledgment, acknowledging message_id_1 would also implicitly acknowledge message_id_2 and message_id_3 because they were received in the same batch. If it were individual, only message_id_1 would be marked as processed. The difference is profound for how Pulsar manages message redelivery.

The core problem Pulsar’s acknowledgment modes solve is ensuring exactly-once or at-least-once processing semantics while minimizing redundant work. If a consumer crashes mid-batch, how does Pulsar know which messages were definitely processed and which need redelivery?

With cumulative acknowledgment, the consumer tells the broker the highest contiguous sequence ID of messages it has successfully processed. When a consumer acknowledges message ID X, Pulsar marks all messages with IDs less than or equal to X as delivered for that subscription. This is efficient because the broker only needs to track one number per subscription. However, it’s an all-or-nothing proposition for a batch. If the consumer successfully processes message 1 but fails on message 2 in a batch of 1, 2, and 3, acknowledging message 1 would also mark message 2 as processed, leading to data loss. This mode is best suited for scenarios where messages are idempotent or where slight message loss is acceptable.

With individual acknowledgment, the consumer explicitly acknowledges each message ID it successfully processes. If the consumer receives messages 1, 2, and 3, it can acknowledge 1, then 2, and then fail before acknowledging 3. Pulsar will then only redeliver messages 3 (and any subsequent ones it didn’t receive). This provides stronger guarantees but requires the consumer to keep track of individual message IDs and send acknowledgments for each, increasing network traffic and processing overhead on both client and broker.

Here’s how you configure it. In your consumer application, when creating the subscription, you specify the acknowledgment type:

// For cumulative acknowledgment
Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("persistent://my-tenant/my-namespace/my-topic")
    .subscriptionName("my-subscription-cumulative")
    .subscriptionType(SubscriptionType.Exclusive) // Or Failover, Shared
    .ackReceiptEnabled(false) // Default is false, meaning cumulative
    .subscribe();

// For individual acknowledgment
Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("persistent://my-tenant/my-namespace/my-topic")
    .subscriptionName("my-subscription-individual")
    .subscriptionType(SubscriptionType.Exclusive) // Or Failover, Shared
    .ackReceiptEnabled(true) // Explicitly enable ack receipt for individual acks
    .subscribe();

The ackReceiptEnabled(true) flag is the key differentiator in the Java client for enabling individual acknowledgments. For the pulsar-client CLI, you use the --ack-message flag with the specific message ID for individual acks, and for cumulative, you’d typically acknowledge the last processed message’s ID.

The one thing that trips many people up is how Pulsar handles redelivery with cumulative acks when a consumer disconnects. If a consumer is processing messages in batches, and it acknowledges message X (cumulatively), but then crashes before processing message X+1 (which was in the same batch), Pulsar, upon detecting the consumer’s failure, will reset the cursor for that subscription to X+1. This appears to be individual acknowledgment behavior at first glance, but it’s actually Pulsar’s way of handling batch processing with cumulative acks: it assumes everything up to the acknowledged ID is good, and anything after the acknowledged ID in the same batch that wasn’t explicitly acknowledged (or implicitly acknowledged by a later cumulative ack) needs redelivery. The actual redelivery threshold is determined by the last successfully processed message ID that the broker is aware of, which is what the cumulative ack updates.

The next concept you’ll grapple with is the interplay between these acknowledgment modes and different subscription types (Exclusive, Failover, Shared, Key_Shared), particularly how message ordering and redelivery are managed across multiple consumers.

Want structured learning?

Take the full Pulsar course →