Skip to main content
Subscriptions are named configurations that track message consumption progress for one or more consumers. Pulsar supports multiple subscription types, each with different semantics.

Subscription Basics

A subscription:
  • Has a name unique within a topic
  • Tracks which messages have been acknowledged
  • Persists even after all consumers disconnect
  • Can be shared by multiple consumers (depending on type)
From pulsar-client-api/src/main/java/org/apache/pulsar/client/api/Consumer.java:
Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("persistent://tenant/ns/topic")
    .subscriptionName("my-subscription")  // Names the subscription
    .subscriptionType(SubscriptionType.Shared)  // Determines behavior
    .subscribe();
Subscriptions are durable - even if all consumers disconnect, the subscription cursor position is preserved. When consumers reconnect, they resume from where they left off.

Subscription Types

From pulsar-client-api/src/main/java/org/apache/pulsar/client/api/SubscriptionType.java, Pulsar supports four subscription types:

Exclusive

Only one consumer can be attached to the subscription at a time.
public enum SubscriptionType {
    /**
     * There can be only 1 consumer on the same topic with the same subscription name.
     */
    Exclusive
}
Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-exclusive-sub")
    .subscriptionType(SubscriptionType.Exclusive)  // Default type
    .subscribe();
Characteristics:
  • Single active consumer per subscription
  • Strict message ordering guaranteed
  • If consumer disconnects, another can take over
  • Attempting to connect a second consumer fails
Use Cases:
  • When processing must be sequential
  • Single-threaded message processing
  • Strict ordering requirements
If you need multiple consumers for higher throughput, use Failover or Shared subscriptions instead.

Failover

Multiple consumers can attach, but only one actively receives messages.
public enum SubscriptionType {
    /**
     * Multiple consumers can use the same subscription name but only 1 consumer 
     * will receive messages. If that consumer disconnects, one of the other 
     * connected consumers will start receiving messages.
     * 
     * In failover mode, the consumption ordering is guaranteed.
     * 
     * In case of partitioned topics, the ordering is guaranteed on a per-partition basis.
     * The partition assignments will be split across available consumers.
     */
    Failover
}
// Consumer 1 - active
Consumer<String> consumer1 = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-failover-sub")
    .subscriptionType(SubscriptionType.Failover)
    .consumerName("consumer-1")
    .subscribe();

// Consumer 2 - standby
Consumer<String> consumer2 = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-failover-sub")
    .subscriptionType(SubscriptionType.Failover)
    .consumerName("consumer-2")
    .subscribe();
Characteristics:
  • One consumer active, others on standby
  • Automatic failover when active consumer disconnects
  • Message ordering preserved
  • For partitioned topics: partitions distributed across consumers
Use Cases:
  • High availability with ordering guarantees
  • Automatic failover without manual intervention
  • Processing that requires ordering but needs redundancy

Shared

Multiple consumers receive messages in round-robin distribution.
public enum SubscriptionType {
    /**
     * Multiple consumers will be able to use the same subscription name and 
     * messages will be dispatched according to a round-robin rotation between 
     * connected consumers.
     * 
     * In this mode, the consumption order is not guaranteed.
     */
    Shared
}
// Consumer 1
Consumer<String> consumer1 = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-shared-sub")
    .subscriptionType(SubscriptionType.Shared)
    .subscribe();

// Consumer 2
Consumer<String> consumer2 = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-shared-sub")
    .subscriptionType(SubscriptionType.Shared)
    .subscribe();
Characteristics:
  • Messages distributed round-robin across all consumers
  • No ordering guarantees
  • Maximum parallelism
  • Individual message acknowledgment required
Use Cases:
  • High-throughput scenarios
  • Order-independent processing
  • Scaling consumption horizontally
  • Worker queue patterns
Shared subscriptions provide the highest throughput by distributing messages across all available consumers.

Key_Shared

Messages with the same key go to the same consumer.
public enum SubscriptionType {
    /**
     * Multiple consumers can use the same subscription and all messages with 
     * the same key will be dispatched to only one consumer.
     * 
     * Use ordering_key to overwrite the message key for message ordering.
     */
    Key_Shared
}
Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-key-shared-sub")
    .subscriptionType(SubscriptionType.Key_Shared)
    .subscribe();

// Producer sends messages with keys
producer.newMessage()
    .key("user-123")  // All messages with this key go to same consumer
    .value("order data")
    .send();
Characteristics:
  • Ordering guaranteed per key
  • Parallel processing across different keys
  • Keys distributed across consumers using hash
  • Can use orderingKey instead of message key
Use Cases:
  • Per-entity ordering (e.g., per-user, per-device)
  • Stateful processing where related messages must be processed together
  • Combining parallelism with partial ordering
Messages without a key are distributed round-robin. Always set keys for messages when using Key_Shared subscriptions.

Subscription Modes

From pulsar-client-api/src/main/java/org/apache/pulsar/client/api/SubscriptionMode.java:
Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-sub")
    .subscriptionMode(SubscriptionMode.Durable)  // or NonDurable
    .subscribe();

Durable (Default)

Subscription cursor persists even after all consumers disconnect:
subscriptionMode(SubscriptionMode.Durable)

Non-Durable

Subscription automatically deleted when last consumer disconnects:
subscriptionMode(SubscriptionMode.NonDurable)

Initial Position

From pulsar-client-api/src/main/java/org/apache/pulsar/client/api/SubscriptionInitialPosition.java:
Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-sub")
    .subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)  // or Latest
    .subscribe();
Latest (Default): New subscriptions start at the most recent message Earliest: New subscriptions start at the oldest available message
Initial position only applies when creating a new subscription. Existing subscriptions always resume from their last acknowledged position.

Cursor Management

Each subscription maintains a cursor tracking consumption progress:
# View subscription details
pulsar-admin topics stats persistent://tenant/ns/topic

# Reset subscription to specific position
pulsar-admin topics reset-cursor \
  persistent://tenant/ns/topic \
  --subscription my-sub \
  --time "2024-01-01T00:00:00"

# Skip messages
pulsar-admin topics skip \
  persistent://tenant/ns/topic \
  --subscription my-sub \
  --count 1000

Acknowledgment Patterns

Different subscription types support different acknowledgment patterns:

Individual Acknowledgment (All Types)

Message<String> msg = consumer.receive();
processMessage(msg);
consumer.acknowledge(msg);

Cumulative Acknowledgment (Exclusive/Failover Only)

Message<String> msg = consumer.receive();
processMessage(msg);
consumer.acknowledgeCumulative(msg);
// All messages up to and including 'msg' are acknowledged
Cumulative acknowledgment is not supported with Shared or Key_Shared subscriptions because message ordering is not guaranteed.

Batch Acknowledgment

Messages<String> messages = consumer.batchReceive();
for (Message<String> msg : messages) {
    processMessage(msg);
}
consumer.acknowledge(messages);  // Acknowledge all at once

Subscription Backlog

The backlog is the number of unacknowledged messages:
# View backlog for all subscriptions
pulsar-admin topics stats persistent://tenant/ns/topic

# Clear backlog (skip all unacknowledged messages)
pulsar-admin topics clear-backlog \
  persistent://tenant/ns/topic \
  --subscription my-sub

Subscription Comparison

FeatureExclusiveFailoverSharedKey_Shared
Multiple ConsumersNoYes (one active)Yes (all active)Yes (all active)
Message OrderingGuaranteedGuaranteedNot guaranteedPer-key
Cumulative AckYesYesNoNo
ThroughputLowMediumHighHigh
Use CaseSequential processingHA with orderingHigh throughputPer-key ordering

Advanced Configuration

Dead Letter Topics

Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-sub")
    .deadLetterPolicy(DeadLetterPolicy.builder()
        .maxRedeliverCount(5)
        .deadLetterTopic("my-topic-DLQ")
        .build())
    .subscribe();

Negative Acknowledgment Delay

Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-sub")
    .negativeAckRedeliveryDelay(60, TimeUnit.SECONDS)
    .subscribe();

Acknowledgment Timeout

Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("my-topic")
    .subscriptionName("my-sub")
    .ackTimeout(30, TimeUnit.SECONDS)
    .subscribe();

Next Steps

Producers & Consumers

Learn about client implementation details

Messaging

Understand message delivery semantics

Topics

Explore topic types and configuration

Schemas

Learn about schema evolution

Build docs developers (and LLMs) love