---
source_url: https://www.pubnub.com/docs/sdks/java/api-reference/publish-and-subscribe
title: Publish/Subscribe API for Java SDK
updated_at: 2026-06-05T11:12:12.695Z
sdk_name: PubNub Java SDK
sdk_version: 6.4.5
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Publish/Subscribe API for Java SDK

PubNub Java SDK, use the latest version: 6.4.5

Install:

```bash
Add PubNub dependency to your build@6.4.5
```

:::warning Breaking changes in v9.0.0
PubNub Kotlin SDK version 9.0.0 unifies the codebases for Kotlin and [Java](https://www.pubnub.com/docs/sdks/java) SDKs, introduces a new way of instantiating the PubNub client, and changes asynchronous API callbacks and emitted [status events](https://www.pubnub.com/docs/sdks/kotlin/status-events). These changes can impact applications built with previous versions (< `9.0.0` ) of the Kotlin SDK.
For more details about what has changed, refer to [Java/Kotlin SDK migration guide](https://www.pubnub.com/docs/sdks/kotlin/migration-guides/kotlin-v9-migration-guide).
:::

PubNub delivers messages worldwide in less than 30 ms. Send a message to one recipient or broadcast to thousands of subscribers.

For higher-level conceptual details on publishing and subscribing, refer to [Connection Management](https://www.pubnub.com/docs/general/setup/connection-management) and to [Publish Messages](https://www.pubnub.com/docs/general/messages/publish).

## Publish

##### Available in entities

This method is available to use with the `Channel` entity. For more information, refer to [Channel](https://www.pubnub.com/docs/sdks/java/entities/channel).

`publish()` sends a message to all channel subscribers. PubNub replicates the message across its points of presence and delivers it to all subscribed clients on that channel.

###### Prerequisites and limitations

* You must [initialize PubNub](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#configuration) with the `publishKey`.
* You must create a [Channel](https://www.pubnub.com/docs/sdks/java/entities/channel) entity where you will publish to.
* You don't have to be subscribed to a channel to publish to it.
* You cannot publish to multiple channels simultaneously.

###### Security

Secure messages with Transport Layer Security (TLS) or Secure Sockets Layer (SSL) by setting `ssl` to `true` during [initialization](https://www.pubnub.com/docs/sdks/java/api-reference/configuration). You can also [encrypt](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#cryptomodule) messages.

###### Message data

The message can contain any JavaScript Object Notation (JSON)-serializable data (objects, arrays, integers, strings). Avoid special classes or functions. Strings can include any UTF‑8 characters.

:::warning Don't JSON serialize
You should not JSON serialize the `message` and `meta` parameters when sending signals, messages, or files as the serialization is automatic. Pass the full object as the message/meta payload and let PubNub handle everything.
:::

###### Size

The maximum message size is 32 KiB. This includes the escaped character count and the channel name. Aim for under 1,800 bytes for optimal performance.

If your message exceeds the limit, you'll receive a `Message Too Large` error. To learn more or calculate payload size, see [Message size limits](https://www.pubnub.com/docs/general/messages/publish#message-size-limit).

:::tip Need larger messages?
Our platform is optimized for payloads up to 32 KiB. PubNub supports larger messages, but increasing the limit requires a verification of compatibility with your use case.
Talk to [our team](https://www.pubnub.com/company/contact-sales/) to discuss increasing the message size limit for your use case.
:::

###### Publish rate

You can publish as fast as bandwidth allows. There is a [soft throughput limit](https://www.pubnub.com/docs/general/setup/limits) because messages may drop if subscribers can't keep up.

For example, publishing 200 messages at once may cause the first 100 to drop if a subscriber hasn't received any yet. The in-memory queue stores only 100 messages.

###### Custom message type

You can optionally provide the `customMessageType` parameter to add your business-specific label or category to the message, for example `text`, `action`, or `poll`.

###### Best practices

* Publish to a channel serially (not concurrently).
* Verify a success return code (for example, `[1,"Sent","136074940..."]`).
* Publish the next message only after a success return code.
* On failure (`[0,"blah","<timetoken>"]`), retry.
* Keep the in-memory queue under 100 messages to avoid drops.
* Throttle bursts to meet latency needs (for example, no more than 5 messages per second).

### Method(s)

To publish to a channel, you must first create a [Channel entity](https://www.pubnub.com/docs/sdks/java/entities/channel) where you provide the name of the channel you want to publish to.

```java
Channel channel = pubnub.channel("myChannel");

channel.publish(Object message)
    .shouldStore(Boolean)
    .meta(Object)
    .queryParam(HashMap)
    .usePOST(Boolean)
    .ttl(Integer);
    .customMessageType(String)
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| message | Object | Yes |  | The payload. |
| shouldStore | Boolean | Optional | `account default` | Store in history. If `shouldStore` is not specified, then the history configuration on the key is used. |
| meta | Object | Optional | `Not set` | `Meta` data object which can be used with the filtering ability. |
| queryParam | HashMap<string,string> | Optional | `Not set` | One or more query parameters to be passed to the server, for analytics purposes. Overridden in case of conflicts with reserved PubNub parameters, such as `uuid` or `instance_id`. Accessible from your PubNub Dashboard, and never returned in server responses. |
| usePOST | Boolean | Optional | `false` | Use POST to `publish`. |
| ttl | Integer | Optional |  | Set a per message time to live in Message Persistence. If shouldStore = true, and ttl = 0, the message is stored with no expiry time., If shouldStore = true and ttl = X (X is an Integer value), the message is stored with an expiry time of X hours unless you have message retention set to Unlimited on your keyset configuration in the Admin Portal., If shouldStore = false, the ttl parameter is ignored., If ttl is not specified, then expiration of the message defaults back to the expiry value for the key. |
| customMessageType | String | Optional |  | A case-sensitive, alphanumeric string from 3 to 50 characters describing the business-specific label or category of the message. Dashes `-` and underscores `_` are allowed. The value cannot start with special characters or the string `pn_` or `pn-`. Examples: `text`, `action`, `poll`. |
| sync | Command | Optional |  | Block the thread, exception thrown if something goes wrong. |
| async | Consumer<Result> | Optional |  | `Consumer` of a `Result` of type `PNPublishResult` |

### Sample code

:::tip Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
:::

#### Publish a message to a channel

```java
import com.google.gson.JsonObject;
import com.pubnub.api.PubNubException;
import com.pubnub.api.java.PubNub;
import com.pubnub.api.java.v2.PNConfiguration;
import com.pubnub.api.UserId;
import com.pubnub.api.java.v2.entities.Channel;
import com.pubnub.api.models.consumer.PNPublishResult;

public class PublishApp {
    public static void main(String[] args) throws PubNubException {
        // Configure PubNub instance
        PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("demoUserId"), "demo");
        configBuilder.publishKey("demo");
        configBuilder.secure(true);

        PubNub pubnub = PubNub.create(configBuilder.build());

        // Create a channel to publish to
        Channel channel = pubnub.channel("myChannel");

        // Create a message to publish
        JsonObject position = new JsonObject();
        position.addProperty("lat", 32L);
        position.addProperty("lng", 32L);

        // Publish the message
        channel.publish(position)
                .customMessageType("text-message")
                .async(result -> {
                    result.onSuccess((PNPublishResult res) -> {
                        System.out.println("Publish timetoken: " + res.getTimetoken());
                    }).onFailure((PubNubException exception) -> {
                        System.out.println("Error publishing message: " + exception.getMessage());
                    });
                });
    }
}
```

:::note Subscribe to the channel
Before running the above publish example, either using the [Debug Console](https://www.pubnub.com/docs/console/) or in a separate script running in a separate terminal window, [subscribe to the same channel](#subscribe) that is being published to.
:::

### Other examples

#### Publish with metadata

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);
Channel channel = pubnub.channel("myChannel");

channel.publish(Arrays.asList("hello", "there"))
        .customMessageType("text-message")
        .shouldStore(true)
        .meta("meta") // optional meta data object which can be used with the filtering ability.
        .usePOST(true)
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("Message published with timetoken: " + publishResult.getTimetoken());
            }).onFailure( (PubNubException exception) -> {
                System.out.println("Error publishing message: " + exception.getMessage());
            });
        });
```

#### Publishing JsonObject (Google GSON)

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);
Channel channel = pubnub.channel("myChannel");

JsonObject position = new JsonObject();
position.addProperty("lat", 32L);
position.addProperty("lng", 32L);

System.out.println("position: " + position);

PNPublishResult publishResult = channel.publish(position)
        .customMessageType("text-message")
        .sync();

System.out.println("PubNub timetoken: " + publishResult.getTimetoken());
```

#### Publishing JsonArray (Google GSON)

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);
Channel channel = pubnub.channel("myChannel");

JsonArray position = new JsonArray();
position.add(32L);

System.out.println("before pub: " + position);

channel.publish(position)
        .customMessageType("text-message")
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("PubNub timetoken: " + publishResult.getTimetoken());
            });
        });
```

#### Publishing JSONObject (org.json)

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);
Channel channel = pubnub.channel("myChannel");

JSONObject position = new JSONObject();
position.put("lat", 32L);
position.put("lng", 32L);

System.out.println("before pub: " + position);
PNPublishResult publishResult = channel.publish(position.toMap())
        .customMessageType("text-message")
        .sync();

System.out.println("PubNub timetoken: " + publishResult.getTimetoken());
```

#### Publishing JSONArray (org.json)

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);
Channel channel = pubnub.channel("myChannel");

JSONArray position = new JSONArray();
position.put(32L);

System.out.println("before pub: " + position);

channel.publish(position.toList())
        .customMessageType("text-message")
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("PubNub timetoken: " + publishResult.getTimetoken());
            });
        });
```

#### Store the published message for 10 hours

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);
Channel channel = pubnub.channel("myChannel");

PNPublishResult publishResult = channel.publish("test-message")
        .customMessageType("text-message")
        .shouldStore(true)
        .ttl(10)
        .sync();

System.out.println("PubNub timetoken: " + publishResult.getTimetoken());
```

### Response

The `publish()` operation returns a `PNPublishResult`:

| Method | Description |
| --- | --- |
| `getTimetoken()`Type: Long | Returns a `long` representation of the timetoken when the signal was published. |

## Fire

##### Available in entities

This method is available to use with the `Channel` entity. For more information, refer to [Channel](https://www.pubnub.com/docs/sdks/java/entities/channel).

The fire endpoint sends a message to Functions event handlers and [Illuminate](https://www.pubnub.com/docs/illuminate/business-objects/external-data-sources). The message goes directly to handlers registered on the target channel and triggers their execution. The handler can read the request body. Messages sent via `fire()` aren't replicated to subscribers and aren't stored in history.

###### Prerequisites and limitations

* You must [initialize PubNub](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#configuration) with the `publishKey`.
* You must create a [Channel](https://www.pubnub.com/docs/sdks/java/entities/channel) entity where you will fire to.
* The message sent via `fire()` isn't replicated and won't be received by subscribers.
* The message is not stored in history.

### Method(s)

```java
Channel channel = pubnub.channel("myChannel");

channel.fire(Object message)
    .meta(Object)
    .usePOST(Boolean);
```

| Parameter | Description |
| --- | --- |
| `message` *Type: ObjectDefault: n/a | The payload. |
| `meta`Type: ObjectDefault: `Not set` | `Meta` data object which can be used with the filtering ability. |
| `usePOST`Type: BooleanDefault: `false` | Use POST to `publish`. |
| `sync`Type: CommandDefault: n/a | Block the thread, exception thrown if something goes wrong. |
| `async`Type: `Consumer<Result>`Default: n/a | `Consumer` of a `Result` of type `PNPublishResult` |

### Sample code

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);
Channel channel = pubnub.channel("myChannel");

channel.fire(Arrays.asList("hello", "there"))
        .usePOST(true)
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("publish worked! timetoken: " + publishResult.getTimetoken());
            }).onFailure( (PubNubException exception) -> {
                System.out.println("error happened while publishing: " + exception.toString());
            });
        });
```

### Response

The `fire()` operation returns a `PNPublishResult`:

| Method | Description |
| --- | --- |
| `getTimetoken()`Type: Long | Returns a `long` representation of the timetoken when the signal was published. |

## Signal

##### Available in entities

This method is available to use with the `Channel` entity. For more information, refer to [Channel](https://www.pubnub.com/docs/sdks/java/entities/channel).

The `signal()` function sends a signal to all subscribers of a channel.

###### Prerequisites and limitations

* You must [initialize PubNub](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#configuration) with the `publishKey`.
* You must create a [Channel](https://www.pubnub.com/docs/sdks/java/entities/channel) entity where you will fire to.
* The message payload size (without the URI or headers) is limited to `64` bytes. If you require a larger payload size, [contact support](https://www.pubnub.com/docs/mailto:support@pubnub.com).

###### Signal vs. Message

| **Feature** | **Signals** | **Messages** |
| --- | --- | --- |
| **Payload size** | Limited to 64 bytes (64B) | Up to 32 KiB |
| **Cost efficiency** | Cost less than standard messages | Generally more expensive than signals |
| **Persistence** | Cannot be saved in [Message Persistence](https://www.pubnub.com/docs/sdks/java/api-reference/storage-and-playback) (past signals cannot be accessed) | Can be saved and accessed through Message Persistence |
| **Push Notifications** | Cannot trigger [Mobile Push Notifications](https://www.pubnub.com/docs/sdks/java/api-reference/mobile-push) | Can trigger Mobile Push Notifications |
| **Use case suitability** | Best for non-critical data streams, like geolocation updates | Suitable for critical and non-critical use cases |
| **Metadata support** | Do not support metadata | Support metadata |

:::note Channel separation
Signals and messages should be sent on separate channels to improve connection recovery behaviour.
:::

### Method(s)

```java
Channel channel = pubnub.channel("myChannel");

channel.signal(Object message)
    .customMessageType(String);
```

| Parameter | Description |
| --- | --- |
| `message` *Type: Object | The payload which will be serialized and sent. |
| `customMessageType`Type: String | A case-sensitive, alphanumeric string from 3 to 50 characters describing the business-specific label or category of the message. Dashes `-` and underscores `_` are allowed. The value cannot start with special characters or the string `pn_` or `pn-`. Examples: `text`, `action`, `poll`. |
| `sync`Type: PNPublishResult | Executes the call. Blocks the thread, exception is thrown if something goes wrong. |
| `async`Type: `Consumer<Result>` | Executes the call asynchronously. |

### Sample code

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);
Channel channel = pubnub.channel("myChannel");

channel.signal("Hello everyone!")
        .customMessageType("text-message")
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                Long timetoken = publishResult.getTimetoken(); // signal message timetoken
            }).onFailure( (PubNubException exception) -> {
                exception.printStackTrace();
            });
        });
```

### Response

The `signal()` operation returns a `PNPublishResult`:

| Method | Description |
| --- | --- |
| `getTimetoken()`Type: Long | Returns a `long` representation of the timetoken when the signal was published. |

## Subscribe

Subscribe opens a TCP socket and listens for messages and events on a specified entity or set of entities. Set `subscribeKey` during [initialization](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#initialization).

:::tip Conceptual overview
For more general information about subscriptions, refer to [Subscriptions](https://www.pubnub.com/docs/general/channels/subscribe).
:::

Entities are [first-class citizens](https://en.wikipedia.org/wiki/First-class_citizen) that expose their APIs. You can subscribe using the PubNub client or directly on a specific entity:

* [Channel](https://www.pubnub.com/docs/sdks/java/entities/channel)
* [ChannelGroup](https://www.pubnub.com/docs/sdks/java/entities/channel-group)
* [UserMetadata](https://www.pubnub.com/docs/sdks/java/entities/user-metadata)
* [ChannelMetadata](https://www.pubnub.com/docs/sdks/java/entities/channel-metadata)

After `subscribe()`, the client receives new messages. Configure [retryConfiguration](https://www.pubnub.com/docs/sdks/java/api-reference/configuration) to reconnect automatically after disconnects.

### Subscription scope

Subscriptions let you attach listeners for specific real-time update types. Your app receives messages and events through those listeners. There are two types:

* [Subscription](#create-a-subscription): created from an entity and scoped to that entity (for example, a particular channel)
* [SubscriptionSet](#create-a-subscription-set): created from the PubNub client and scoped to the client (for example, all subscriptions created on a single `pubnub` object). A set can include one or more subscriptions.

One event listener receives all messages, signals, and events for the entities you subscribe to. To add listeners, see [Event listeners](#event-listeners).

### Create a subscription

:::note Managing subscription lifecycle
The `Subscription` object implements the [AutoCloseable](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html) interface to help you release resources by unsubscribing and removing all listeners. Always call `Subscription.close()` when you no longer need this `Subscription`.
:::

An entity-level `Subscription` allows you to receive messages and events for only that entity for which it was created. Using multiple entity-level `Subscription`s is useful for handling various message/event types differently in each channel.

```java
// Entity-based, local-scoped

channel.subscription(SubscriptionOptions options)
```

| Parameter | Description |
| --- | --- |
| `options`Type: `SubscriptionOptions` | `Subscription` [behavior configuration](#subscriptionoptions). Use `null` for no specific options. |

#### Sample code

```java
// Entity-based, local-scoped

// Specify the channel for subscription
Channel myChannel = pubnub.channel("channelName");

// Create subscription options, if any
SubscriptionOptions options = SubscriptionOptions.receivePresenceEvents();

// Return a Subscription object that is used to establish the subscription
Subscription subscription = myChannel.subscription(options);

// Activate the subscription to start receiving events
subscription.subscribe();
```

### Create a subscription set

:::note Managing subscription lifecycle
The `SubscriptionSet` object implements the [AutoCloseable](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html) interface to help you release resources by unsubscribing and removing all listeners. Always call `SubscriptionSet.close()` when you no longer need this `SubscriptionSet`.
:::

A client-level `SubscriptionSet` allows you to receive messages and events for all entities in the set. A single `SubscriptionSet` is useful for similarly handling various message/event types in each channel.

```java
// client-based, general-scoped

pubnub.subscriptionSetOf(
    Set<String> channels, 
    Set<String> channelGroups, 
    SubscriptionOptions options
)
```

| Parameter | Description |
| --- | --- |
| `> channels`Type: `Set<String>` | Set of channel names to subscribe to. Use an empty set for no channels. |
| `> channelGroups`Type: `Set<String>` | Set of channel group names to subscribe to. Use an empty set for no channel groups. |
| `> options`Type: `SubscriptionOptions` | Additional subscription configuration to define the subscription behavior. If you don't set any options, `EmptyOptions` is used by default. |

:::tip Add/remove sets
You can add and remove subscription sets to create new sets. Refer to the [Other examples](#other-examples-1) section for more information.
:::

#### SubscriptionOptions

`SubscriptionOptions` is a class designed to configure subscription behaviors with optional modifiers. When no specific options are required, `EmptyOptions` is set by default.

The class includes:

| Option | Description |
| --- | --- |
| `receivePresenceEvents()` | Whether presence updates for `userId`s should be delivered through the listener streams. For information on how to receive presence events and what those events are, refer to [Presence Events](https://www.pubnub.com/docs/general/presence/presence-events#subscribe-to-presence-channel). |
| `filter(predicate: (PNEvent) -> Boolean)` | Allows for custom filtering of events delivered to the subscription based on the provided predicate. Useful for event-specific handling. |

#### Sample code

```java
// Step 1: Create a subscription set
Subscription mySubscription01 = pubNub.channel("my_channel01").subscription();
Subscription mySubscription02 = pubNub.channel("my_channel02").subscription();
Set<Subscription> subscriptions = new HashSet<>();
subscriptions.add(mySubscription01);
subscriptions.add(mySubscription02);

SubscriptionSet mySubscriptionSet = pubNub.subscriptionSetOf(subscriptions);

// Step 2: Subscribe using the subscription set
mySubscriptionSet.subscribe();
```

### Method(s)

`Subscription` and `SubscriptionSet` use the same methods to subscribe:

* [Subcribe](#subscribe-1)
* [Subscribe with timetoken](#subscribe-with-timetoken)

#### Subscribe

To subscribe, you can use the following method:

```java
// For subscription
subscription.subscribe();
// For subscription set
subscriptionSet.subscribe();
```

##### Sample code

```java
// Entity-based, local-scoped

// Specify the channel for subscription
Channel myChannel = pubnub.channel("channelName");

// Create subscription options, if any
SubscriptionOptions options = SubscriptionOptions.receivePresenceEvents();

// Return a Subscription object that is used to establish the subscription
Subscription subscription = myChannel.subscription(options);

// Activate the subscription to start receiving events
subscription.subscribe();
// Step 1: Create a subscription set
Subscription mySubscription01 = pubNub.channel("my_channel01").subscription();
Subscription mySubscription02 = pubNub.channel("my_channel02").subscription();
Set<Subscription> subscriptions = new HashSet<>();
subscriptions.add(mySubscription01);
subscriptions.add(mySubscription02);

SubscriptionSet mySubscriptionSet = pubNub.subscriptionSetOf(subscriptions);

// Step 2: Subscribe using the subscription set
mySubscriptionSet.subscribe();
```

##### Other examples

###### Create a subscription set and add/remove subscriptions

```java
// Create subscriptions
Subscription subscription1 = pubNub.channel("channelName").subscription();
Subscription subscription2 = pubNub.channelGroup("channelGroup").subscription();
Subscription subscription3 = pubNub.channel("channelName03").subscription();

// Combine into a subscription set
SubscriptionSet subscriptionSet = subscription1.plus(subscription2);

// Add another subscription to the set
subscriptionSet.add(subscription3);

// Remove a subscription from the set
subscriptionSet.remove(subscription3);
```

##### Returns

The `subscribe()` method doesn't have a return value.

#### Subscribe with timetoken

:::warning Impact on other subscriptions
Subscribing with a timetoken affects all other subscriptions because it overwrites the timetoken in the single PubNub server connection in the SDK. However, those other subscriptions will not deliver messages older than ones that were already delivered - after receiving an event, a subscription only gets future events, ignoring those before or at the time of the last event received.
:::

To subscribe to real-time updates from a given timetoken, use the following method:

```java
subscriptionSet.subscribe(SubscriptionCursor(timetoken = yourTimeToken))
```

| Parameter | Description |
| --- | --- |
| `cursor` *Type: `SubscriptionCursor` | Cursor from which to return any available cached messages. `SubscriptionCursor` would typically include a `timetoken` (long integer) representing the point in time from which to receive updates. |

##### Sample code

```java
// Create subscription options, if any
SubscriptionOptions options = SubscriptionOptions.receivePresenceEvents();

// Define the channels to subscribe to
Subscription subscription01 = pubNub.channel("my_channel01").subscription(options);
Subscription subscription02 = pubNub.channel("my_channel02").subscription();
Set<Subscription> subscriptions = new HashSet<>();
subscriptions.add(subscription01);
subscriptions.add(subscription02);

// Create a subscription set with specified channels and subscription options
SubscriptionSet subscriptionSet = pubNub.subscriptionSetOf(subscriptions);

// Define the timetoken for where the subscription should start
Long yourTimeToken = 100000000000L; // Directly using Long type

// Subscribe to the created SubscriptionSet with the desired timetoken
subscriptionSet.subscribe(new SubscriptionCursor(yourTimeToken));
```

##### Returns

The method for subscribing with a timetoken doesn't have a return value.

## Event listeners

Messages and events are received in your app using a listener. This listener allows a single point to receive all messages, signals, and events.

You can attach listeners to the instances of [Subscription](#create-a-subscription), [SubscriptionSet](#create-a-subscription-set), and, in the case of the connection status, the PubNub client.

:::note No built-in event throttling
The PubNub SDK delivers every incoming event to your listener as it arrives — there is no built-in throttling or rate-limiting on the subscriber side. If you need to control how often your application processes events, wrap your listener callback with a throttle or debounce utility from your language or framework ecosystem.
To reduce the number of messages delivered to your client in the first place, use [Subscribe Filters](https://www.pubnub.com/docs/general/channels/subscribe-filters) to filter messages server-side before they reach your listener.
:::

### Add listeners

You can add listeners for various types of updates related to your subscription. You can implement listeners for general updates (that handle multiple event types at once) or choose listeners dedicated to specific event types such as `Message` or `File`.

#### Handle multiple event types

##### Method(s)

```java
fun addListener(listener: EventListener)
```

##### Sample code

```java
// Create a subscription to a specific channel
Subscription subscription = pubNub.channel("my_channel").subscription(SubscriptionOptions.receivePresenceEvents());

subscription.addListener(new EventListener() {
    @Override
    public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult result) {
        // Log or process message
        System.out.println("Message: " + result.getMessage());
    }

    @Override
    public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult result) {
        // Handle presence updates
        // requires a subscription with presence
        System.out.println("Presence userId: " + result.getUuid() + ", Event: " + result.getEvent());
    }

    @Override
    public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult result) {
        // Handle signals
        System.out.println("Signal: " + result.getMessage());
    }

    @Override
    public void messageAction(@NotNull PubNub pubNub, @NotNull PNMessageActionResult result) {
        // Handle message reactions
        System.out.println("Message Reaction: " + result.getData());
    }

    @Override
    public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult result) {
        // Handle file events
        System.out.println("File: " + result.getFile().getName());
    }

    @Override
    public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) {
        // Handle uuid metadata events
        System.out.println("UUID: " + pnUUIDMetadataResult.getData().getName());
    }

    @Override
    public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) {
        // Handle channel metadata events
        System.out.println("Channel: " + pnChannelMetadataResult.getData().getName());
    }

    @Override
    public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) {
        // Handle membership metadata events
        System.out.println("Channel: " + pnMembershipResult.getData().getChannel().getName() + " UUID: "
                + pnMembershipResult.getData().getUuid());
    }
});

// Activate the subscription to start receiving events
subscription.subscribe();

// Print a status when successfully subscribed
System.out.println("Subscribed to channel 'my_channel'");

Subscription subscription02 = pubNub.channel("my_channel02").subscription();
Set<Subscription> subscriptionsSet = new HashSet<>();
subscriptionsSet.add(subscription);
subscriptionsSet.add(subscription02);

SubscriptionSet subscriptionSet = pubNub.subscriptionSetOf(subscriptionsSet);

// add global listener that listen on all subscribed channels
pubNub.addListener(new EventListener() {
    @Override
    public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) {
        System.out.println("Message: " + message.getMessage());
    }
});

// Activate the subscriptionSet to start receiving events
subscriptionSet.subscribe();
```

#### Handle one event type

#### Method(s)

You can also directly register listeners for specific event types on the subscription object by assigning lambda expressions. This method allows you to handle events such as messages, signals, message actions, files, objects, and presence.

Using this method, you cannot have multiple listeners attached to the same event type. Assigning a new listener with this method overwrites the previous one.

##### Sample code

```java
subscription.setOnMessage((PNMessageResult pnMessageResult) -> {
    JsonElement message = pnMessageResult.getMessage();
    String channel1 = pnMessageResult.getChannel();
    String publisher = pnMessageResult.getPublisher();
    String subscription1 = pnMessageResult.getSubscription();
    String customMessageType = pnMessageResult.getCustomMessageType();
    Long timetoken = pnMessageResult.getTimetoken();
});

subscription.setOnSignal((PNSignalResult pnSignalResult) -> {
    JsonElement message = pnSignalResult.getMessage();
    String channel = pnSignalResult.getChannel();
    String publisher = pnSignalResult.getPublisher();
    String subscription1 = pnSignalResult.getSubscription();
    String customMessageType = pnSignalResult.getCustomMessageType();
    Long timetoken = pnSignalResult.getTimetoken();
});

subscription.setOnMessageAction((PNMessageActionResult pnMessageActionResult) -> {
    PNMessageAction messageAction = pnMessageActionResult.getMessageAction();
    String channel = pnMessageActionResult.getChannel();
    String event = pnMessageActionResult.getEvent();
    String publisher = pnMessageActionResult.getPublisher();
    String subscription1 = pnMessageActionResult.getSubscription();
    Long timetoken = pnMessageActionResult.getTimetoken();
});

subscription.setOnFile((PNFileEventResult pnFileEventResult) -> {
    JsonElement message = (JsonElement) pnFileEventResult.getMessage();
    PNDownloadableFile file = pnFileEventResult.getFile();
    String channel = pnFileEventResult.getChannel();
    String publisher = pnFileEventResult.getPublisher();
    String subscription1 = pnFileEventResult.getSubscription();
    String customMessageType = pnFileEventResult.getCustomMessageType();
    Long timetoken = pnFileEventResult.getTimetoken();
});

subscription.setOnUuidMetadata((PNUUIDMetadataResult pnUUIDMetadataResult) -> {
    String event = pnUUIDMetadataResult.getEvent();
    PNUUIDMetadata data = pnUUIDMetadataResult.getData();
    String channel = pnUUIDMetadataResult.getChannel();
    String publisher = pnUUIDMetadataResult.getPublisher();
    String subscription1 = pnUUIDMetadataResult.getSubscription();
    Long timetoken = pnUUIDMetadataResult.getTimetoken();
});

subscription.setOnChannelMetadata((PNChannelMetadataResult pnChannelMetadataResult) -> {
    String event = pnChannelMetadataResult.getEvent();
    PNChannelMetadata data = pnChannelMetadataResult.getData();
    String channel = pnChannelMetadataResult.getChannel();
    String publisher = pnChannelMetadataResult.getPublisher();
    String subscription1 = pnChannelMetadataResult.getSubscription();
    Long timetoken = pnChannelMetadataResult.getTimetoken();
});

subscription.setOnMembership((PNMembershipResult pnMembershipResult) -> {
    String event = pnMembershipResult.getEvent();
    PNMembership data = pnMembershipResult.getData();
    String channel = pnMembershipResult.getChannel();
    String publisher = pnMembershipResult.getPublisher();
    String subscription1 = pnMembershipResult.getSubscription();
    Long timetoken = pnMembershipResult.getTimetoken();
});

subscription.setOnPresence((PNPresenceEventResult pnPresenceEventResult) -> {
    String event = pnPresenceEventResult.getEvent();
    Integer occupancy = pnPresenceEventResult.getOccupancy();
    String channel = pnPresenceEventResult.getChannel();
    List<String> join = pnPresenceEventResult.getJoin();
    List<String> leave = pnPresenceEventResult.getLeave();
    JsonElement state = pnPresenceEventResult.getState();
    List<String> timeout = pnPresenceEventResult.getTimeout();
    String subscription1 = pnPresenceEventResult.getSubscription();
    Long timetoken = pnPresenceEventResult.getTimetoken();
});
```

##### Remove event listener

To remove the listener for a specific event, assign `null` to it.

```java
subscription.setOnMessage(null);
```

### Add connection status listener

Use the `StatusListener` interface with your `PubNub` instance to add a listener dedicated to connection status updates.

:::warning Client scope
This listener is only available on the PubNub object.
:::

#### Method(s)

```java
pubnub.addListener(object : StatusListener() {
    override fun status(pubnub: PubNub, status: PNStatus) {
        // Handle connection status updates
        println("Connection Status: ${status.category}")
    }
})
```

#### Sample code

```java
pubNub.addListener((pubnub, status) -> {
    // Handle connection status updates
    System.out.println("Connection Status: " + status.getCategory());
});
```

#### Returns

This method returns the subscription status and will emit various statuses depending on your client network connection.

To help you adjust your app code, see the [Status Events for Subscribe](https://www.pubnub.com/docs/sdks/java/status-events#subscribe) for the exact mapping between the current and deprecated Kotlin SDK statuses.

For more generic information, head to [SDK Connection Lifecycle](https://www.pubnub.com/docs/general/setup/connection-management#sdk-connection-lifecycle).

## Unsubscribe

Stop receiving real-time updates from a [Subscription](#create-a-subscription) or a [SubscriptionSet](#create-a-subscription-set).

### Method(s)

```java
// For subscription
subscription.unsubscribe();
// For subscription set
subscriptionSet.unsubscribe();
```

### Sample code

```java
// Subscribe to a channel
subscription.subscribe();

// Unsubscribe from that channel
subscription.unsubscribe();
```

### Returns

None

## Unsubscribe all

Stop receiving real-time updates from all listeners and remove the entities associated with them.

:::warning Client scope
This method is only available on the PubNub object.
:::

### Method(s)

```java
pubnub.unsubscribeAll()
```

### Sample code

```java
pubNub.unsubscribeAll();
```

### Returns

None

## Publish (old)

:::warning Not recommended
The use of this method is discouraged. Use [Publish](#publish) instead.
:::

The `publish()` function is used to send a message to all subscribers of a channel. To publish a message you must first specify a valid `publishKey` at initialization. A successfully published message is replicated across the PubNub Real-Time Network and sent simultaneously to all subscribed clients on a channel.

Messages in transit can be secured from potential eavesdroppers with SSL/TLS by setting ssl to true during initialization.

###### Publish anytime

It's not required to be subscribed to a channel in order to publish to that channel.

###### Message data

The message argument can contain any JSON serializable data, including: Objects, Arrays, Ints and Strings. `data` should not contain special Java classes or functions as these will not serialize. String content can include any single-byte or multi-byte UTF-8 character.

:::warning Don't JSON serialize
You should not JSON serialize the `message` and `meta` parameters when sending signals, messages, or files as the serialization is done automatically. Pass the full object as the message/meta payload and let PubNub take care of everything for you.
:::

###### Message size

The maximum number of characters per message is 32 KiB by default. The maximum message size is based on the final escaped character count, including the channel name. An ideal message size is under 1800 bytes which allows a message to be compressed and sent using single IP datagram (1.5 KiB) providing optimal network performance.

If the message you publish exceeds the configured size, you will receive the following message:

###### Message too large error

```java
["PUBLISHED",[0,"Message Too Large","13524237335750949"]]
```

For further details, check [Calculating Message Payload Size Before Publish](https://support.pubnub.com/hc/en-us/articles/360051495932-Calculating-Message-Payload-Size-Before-Publish).

:::tip Need larger messages?
Our platform is optimized for payloads up to 32 KiB. PubNub supports larger messages, but increasing the limit requires a verification of compatibility with your use case.
Talk to [our team](https://www.pubnub.com/company/contact-sales/) to discuss increasing the message size limit for your use case.
:::

###### Message publish rate

Messages can be published as fast as bandwidth conditions will allow. There is a soft limit based on max throughput since messages will be discarded if the subscriber can't keep pace with the publisher.

For example, if 200 messages are published simultaneously before a subscriber has had a chance to receive any messages, the subscriber may not receive the first 100 messages because the message queue has a limit of only 100 messages stored in memory.

###### Publishing to multiple channels

It is not possible to publish a message to multiple channels simultaneously. The message must be published to one channel at a time.

###### Publishing messages reliably

There are some best practices to ensure messages are delivered when publishing to a channel:

* Publish to any given channel in a serial manner (not concurrently).
* Check that the return code is success (for example, `[1,"Sent","136074940..."]`)
* Publish the next message only after receiving a success return code.
* If a failure code is returned (`[0,"blah","<timetoken>"]`), retry the publish.
* Avoid exceeding the in-memory queue's capacity of 100 messages. An overflow situation (aka missed messages) can occur if slow subscribers fail to keep up with the publish pace in a given period of time.
* Throttle publish bursts in accordance with your app's latency needs, for example, Publish no faster than 5 msgs per second to any one channel.

#### Method(s)

To `Publish a message` you can use the following method(s) in the Java SDK:

```java
this.pubnub.publish()
    .message(Object)
    .channel(String)
    .shouldStore(Boolean)
    .meta(Object)
    .queryParam(HashMap)
    .usePOST(Boolean)
    .ttl(Integer);
```

| Parameter | Description |
| --- | --- |
| `message` *Type: ObjectDefault: n/a | The payload. |
| `channel` *Type: StringDefault: n/a | Destination of the `message` (channel ID). |
| `shouldStore`Type: BooleanDefault: `account default` | Store in history. If `shouldStore` is not specified, then the history configuration on the key is used. |
| `meta`Type: ObjectDefault: Not set | `Meta` data object which can be used with the filtering ability. |
| `queryParam`Type: HashMap`<string,string>`Default: Not set | One or more query parameters to be passed to the server, for analytics purposes. Overridden in case of conflicts with reserved PubNub parameters, such as `uuid` or `instance_id`. Accessible from your PubNub Dashboard, and never returned in server responses. |
| `usePOST`Type: BooleanDefault: `false` | Use POST to `publish`. |
| `ttl`Type: IntegerDefault: n/a | Set a per message time to live in Message Persistence. If shouldStore = true, and ttl = 0, the message is stored with no expiry time., If shouldStore = true and ttl = X (X is an Integer value), the message is stored with an expiry time of X hours unless you have message retention set to Unlimited on your keyset configuration in the Admin Portal., If shouldStore = false, the ttl parameter is ignored., If ttl is not specified, then expiration of the message defaults back to the expiry value for the key. |
| `sync`Type: CommandDefault: n/a | Block the thread, exception thrown if something goes wrong. |
| `async`Type: `Consumer<Result>`Default: n/a | `Consumer` of a `Result` of type `PNPublishResult` |

#### Sample code

##### Publish a message to a channel

```java
JsonObject position = new JsonObject();
position.addProperty("lat", 32L);
position.addProperty("lng", 32L);

System.out.println("before pub: " + position);
pubNub.publish()
        .message(position)
        .channel("my_channel")
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("pub timetoken: " + publishResult.getTimetoken());
            });
        });
```

:::note Subscribe to the channel
Before running the above publish example, either using the [Debug Console](https://www.pubnub.com/docs/console/) or in a separate script running in a separate terminal window, [subscribe to the same channel](#subscribe) that is being published to.
:::

#### Returns

The `publish()` operation returns a `PNPublishResult` which contains the following operations:

| Method | Description |
| --- | --- |
| `getTimetoken()`Type: Long | Returns a `long` representation of the timetoken when the message was published. |

#### Other examples

##### Publish with metadata

```java
PNPublishResult pnPublishResult = pubNub.publish()
        .message(Arrays.asList("hello", "there"))
        .channel("suchChannel")
        .shouldStore(true)
        .meta("meta") // optional meta data object which can be used with the filtering ability.
        .usePOST(true)
        .sync();
```

##### Publishing JsonObject (Google GSON)

```java
JsonObject position = new JsonObject();
position.addProperty("lat", 32L);
position.addProperty("lng", 32L);

System.out.println("before pub: " + position);
pubNub.publish()
        .message(position)
        .channel("my_channel")
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("pub timetoken: " + publishResult.getTimetoken());
            });
        });
```

##### Publishing JsonArray (Google GSON)

```java
JsonArray position = new JsonArray();
position.add(32L);

System.out.println("before pub: " + position);
pubNub.publish()
        .message(position)
        .channel("my_channel")
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("pub timetoken: " + publishResult.getTimetoken());
            });
        });
```

##### Publishing JSONObject (org.json)

```java
JSONObject position = new JSONObject();
position.put("lat", 32L);
position.put("lng", 32L);

System.out.println("before pub: " + position);
pubNub.publish()
        .message(position.toMap())
        .channel("my_channel")
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("pub timetoken: " + publishResult.getTimetoken());
            });
        });
```

##### Publishing JSONArray (org.json)

```java
org.json.JSONArray position = new org.json.JSONArray();
position.put(32L);

System.out.println("before pub: " + position);
pubNub.publish()
        .message(position.toList())
        .channel("my_channel")
        .async(result -> {
            result.onSuccess((PNPublishResult publishResult) -> {
                System.out.println("pub timetoken: " + publishResult.getTimetoken());
            });
        });
```

##### Store the published message for 10 hours

```java
PNPublishResult result = pubNub.publish()
        .channel("coolChannel")
        .message("test")
        .shouldStore(true)
        .ttl(10)
        .sync();
```

## Fire (old)

:::warning Not recommended
The use of this method is discouraged. Use [Fire](#fire) instead.
:::

The fire endpoint allows the client to send a message to Functions Event Handlers and [Illuminate](https://www.pubnub.com/docs/illuminate/business-objects/external-data-sources). These messages will go directly to any Event Handlers registered on the channel that you fire to and will trigger their execution. The content of the fired request will be available for processing within the Event Handler. The message sent via `fire()` isn't replicated, and so won't be received by any subscribers to the channel. The message is also not stored in history.

#### Method(s)

To `Fire a message` you can use the following method(s) in the Java SDK:

```java
this.pubnub.fire()
    .message(Object)
    .channel(String)
    .meta(Object)
    .usePOST(Boolean);
```

| Parameter | Description |
| --- | --- |
| `message` *Type: ObjectDefault: n/a | The payload. |
| `channel` *Type: StringDefault: n/a | Destination of the `message` (channel ID). |
| `meta`Type: ObjectDefault: `Not set` | `Meta` data object which can be used with the filtering ability. |
| `usePOST`Type: BooleanDefault: `false` | Use POST to `publish`. |
| `sync`Type: CommandDefault: n/a | Block the thread, exception thrown if something goes wrong. |
| `async`Type: `Consumer<Result>`Default: n/a | `Consumer` of a `Result` of type `PNPublishResult` |

#### Sample code

```java
pubNub.fire()
        .message(Arrays.asList("hello", "there"))
        .channel("my_channel")
        .usePOST(true)
        .async(result -> {
            result.onSuccess((PNPublishResult fireResult) -> {
                System.out.println("publish worked! timetoken: " + fireResult.getTimetoken());
            }).onFailure( (PubNubException exception) -> {
                System.out.println("error happened while calling fire: " + exception.getMessage());
            });
        });
```

#### Response

The `fire()` operation returns a `PNPublishResult`:

| Method | Description |
| --- | --- |
| `getTimetoken()`Type: Long | Returns a `long` representation of the timetoken when the signal was published. |

## Signal (old)

:::warning Not recommended
The use of this method is discouraged. Use [Signal](#signal) instead.
:::

The `signal()` function is used to send a signal to all subscribers of a channel.

By default, signals are limited to a message payload size of `64` bytes. This limit applies only to the payload, and not to the URI or headers. If you require a larger payload size, please [contact support](https://www.pubnub.com/docs/mailto:support@pubnub.com).

#### Method(s)

To `Signal a message` you can use the following method(s) in the Java SDK:

```java
this.pubnub.signal()
    .message(Object)
    .channel(String);
```

| Parameter | Description |
| --- | --- |
| `message` *Type: Object | The payload which will be serialized and sent. |
| `channel` *Type: String | The `channel` ID which the signal will be sent to. |
| `sync`Type: PNPublishResult | Executes the call. Blocks the thread, exception is thrown if something goes wrong. |
| `async`Type: `Consumer<Result>` | Executes the call asynchronously. |

#### Sample code

```java
pubNub.signal()
        .message("Hello everyone!")
        .channel("bar")
        .async(result -> {
            result.onSuccess((PNPublishResult signalResult) -> {
                Long timetoken = signalResult.getTimetoken(); // signal message timetoken
            }).onFailure((PubNubException exception) -> {
                System.out.println("error happened while calling signal: " + exception.getMessage());
            });
        });
```

#### Response

The `signal()` operation returns a `PNPublishResult` object which contains the following operations:

| Method | Description |
| --- | --- |
| `getTimetoken()`Type: Long | Returns a `long` representation of the timetoken when the signal was published. |

## Subscribe (old)

:::warning Not recommended
The use of this method is discouraged. Use [Subscribe](#subscribe) instead.
:::

#### Receive messages

Your app receives messages and events via event listeners. The event listener is a single point through which your app receives all the messages, signals, and events that are sent in any channel you are subscribed to.

For more information about adding a listener, refer to the [Event Listeners](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#event-listeners) for more details. Listeners should be added before calling the method.

#### Description

This function causes the client to create an open TCP socket to the PubNub Real-Time Network and begin listening for messages on a specified `channel` ID. To subscribe to a `channel` ID the client must send the appropriate `subscribeKey` at initialization.

By default a newly subscribed client will only receive messages published to the channel after the `subscribe()` call completes. If a client gets disconnected from a channel, it can automatically attempt to reconnect to that `channel` ID and retrieve any available messages that were missed during that period. This can be achieved by setting `setReconnectionPolicy` to `PNReconnectionPolicy.LINEAR`, when [initializing](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#initialization) the client.

:::warning Unsubscribing from all channels
**Unsubscribing** from all channels, and then **subscribing** to a new channel Y is not the same as subscribing to channel Y and then unsubscribing from the previously-subscribed channel(s). Unsubscribing from all channels resets the last-received `timetoken` and thus, there could be some gaps in the subscription that may lead to message loss.
:::

#### Method(s)

To `Subscribe to a channel` you can use the following method(s) in the Java SDK:

```java
pubnub.subscribe()
    .channels(Array)
    .channelGroups(Arrays)
    .withTimetoken(Long)
    .withPresence()
    .execute();
```

| Parameter | Description |
| --- | --- |
| `channels`Type: Array | Subscribe to `channels`, Either `channel` ID or `channelGroup` is required |
| `channelGroups`Type: Array | Subscribe to `channel groups`, Either `channel` ID or `channelGroup` is required |
| `withTimetoken`Type: Long | Pass a `timetoken` |
| `withPresence()`Type: Command | Also subscribe to related presence information |
| `execute()` *Type: Command | Command that will `execute` `subscribe`. |

#### Sample code

Subscribe to a channel:

```java
pubnub.subscribe()
    .channels(Arrays.asList("my_channel")) // subscribe to channels
    .execute();
```

:::note Event listeners
The response of the call is handled by adding a Listener. Please see the [Listeners section](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#event-listeners).
:::

#### Returns

:::note PNMessageResult
`PNMessageResult` is returned in the [Listeners](https://www.pubnub.com/docs/sdks/java/api-reference/configuration#event-listeners).
:::

The `subscribe()` operation returns a `PNStatus` which contains the following operations:

| Method | Description |
| --- | --- |
| `getCategory()`Type: Enum of type `PNStatusCategory`. | See [Status events for the Java SDK](https://www.pubnub.com/docs/sdks/java/status-events#subscribe). |
| `isError()`Type: Boolean | Is `true` in case of an error. |

The `subscribe()` operation returns a `PNMessageResult` for messages which contains the following operations:

| Method | Description |
| --- | --- |
| `getMessage()`Type: JsonElement | The message sent on the `channel` ID. |
| `getSubscription()`Type: String | The `channel group` or wildcard subscription match (if exists). |
| `getChannel()`Type: String | The `channel` ID for which the message belongs. |
| `getTimetoken()`Type: Long | `Timetoken` for the message. |
| `getUserMetadata()`Type: Object | User `metadata`. |

The `subscribe()` operation returns a `PNPresenceEventResult` from presence which contains the following operations:

| Method | Description |
| --- | --- |
| `getEvent()`Type: String | Events like `join`, `leave`, `timeout`, `state-change`, `interval`. |
| `getUuid()`Type: String | `UUID` for the event. |
| `getTimestamp()`Type: Long | `Timestamp` for the event. |
| `getOccupancy()`Type: Int | Current `occupancy`. |
| `getState()`Type: JsonElement | `State` of the `UUID`. |
| `getSubscription()`Type: String | The `channel group` or wildcard subscription match (if exists). |
| `getChannel()`Type: String | The `channel` ID for which the message belongs. |
| `getTimetoken()`Type: Long | `Timetoken` of the message. |
| `getUserMetadata()`Type: Object | User `metadata`. |

The `subscribe()` operation returns a `PNSignalResult` for signals which contains the following operations:

| Method | Description |
| --- | --- |
| `getMessage()`Type: JsonElement | The signal sent on the `channel` ID. |
| `getSubscription()`Type: String | The `channel group` or wildcard subscription match (if exists). |
| `getChannel()`Type: String | The `channel` ID for which the signal belongs. |
| `getTimetoken()`Type: Long | `Timetoken` for the signal. |
| `getUserMetadata()`Type: Object | User `metadata`. |

#### Other examples

##### Basic subscribe with logging

```java
PNConfiguration.Builder configBuilder = PNConfiguration.builder(new UserId("yourUserId"), "demo");
configBuilder.publishKey("demo");

PNConfiguration pnConfiguration = configBuilder.build();
PubNub pubnub = PubNub.create(pnConfiguration);

pubnub.subscribe()
        .channels(Arrays.asList("my_channel")) // subscribe to channels information
        .execute();
```

##### Subscribing to multiple channels

It's possible to subscribe to more than one channel using the [Multiplexing](https://www.pubnub.com/docs/general/channels/subscribe#channel-multiplexing) feature. The example shows how to do that using an array to specify the channel names.

:::note Alternative subscription methods
You can also use [Wildcard Subscribe](https://www.pubnub.com/docs/general/channels/subscribe#wildcard-subscribe) and [Channel Groups](https://www.pubnub.com/docs/general/channels/subscribe#channel-groups) to subscribe to multiple channels at a time. To use these features, the *Stream Controller* add-on must be enabled on your keyset in the [Admin Portal](https://admin.pubnub.com).
:::

```java
pubnub.subscribe()
    .channels(Arrays.asList("my_channel1","my_channel2" ))
    .execute();
```

##### Subscribing to a Presence channel

:::warning Requires Presence
This method requires that the Presence add-on is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/). For information on how to receive presence events and what those events are, refer to [Presence Events](https://www.pubnub.com/docs/general/presence/presence-events#subscribe-to-presence-channel).
:::

For any given channel there is an associated Presence channel. You can subscribe directly to the channel by appending `-pnpres` to the channel name. For example the channel named `my_channel` would have the presence channel named `my_channel-pnpres`. Presence data can be observed inside the `SubscribeCallback#message(PubNub, PNMessageResult)` callback.

```java
pubNub.subscribe()
        .channels(Arrays.asList("my_channel")) // subscribe to channels
        .withPresence() // also subscribe to related presence information
        .execute();
```

##### Sample Responses

###### Join event

```java
pubNub.addListener(new EventListener() {
    @Override
    public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presenceEvent) {
        if (presenceEvent.getEvent().equals("join")) {
            presenceEvent.getUuid(); // 175c2c67-b2a9-470d-8f4b-1db94f90e39e
            presenceEvent.getTimestamp(); // 1345546797
            presenceEvent.getOccupancy(); // 2
        }
    }
});
```

###### Leave event

```java
pubNub.addListener(new EventListener() {
    @Override
    public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presenceEvent) {
        if (presenceEvent.getEvent().equals("leave")) {
            presenceEvent.getUuid(); // 175c2c67-b2a9-470d-8f4b-1db94f90e39e
            presenceEvent.getTimestamp(); // 1345546797
            presenceEvent.getOccupancy(); // 2
        }
    }
});
```

###### Timeout event

```java
pubNub.addListener(new EventListener() {
    @Override
    public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presenceEvent) {
        if (presenceEvent.getEvent().equals("timeout")) {
            presenceEvent.getUuid(); // 175c2c67-b2a9-470d-8f4b-1db94f90e39e
            presenceEvent.getTimestamp(); // 1345546797
            presenceEvent.getOccupancy(); // 2
        }
    }
});
```

###### State change event

```java
pubNub.addListener(new EventListener() {
    @Override
    public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presenceEvent) {
        if (presenceEvent.getEvent().equals("state-change")) {
            presenceEvent.getState(); // new state
        }
    }
});
```

###### Interval event

```java
pubNub.addListener(new EventListener() {
    @Override
    public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult presenceEvent) {
        if (presenceEvent.getEvent().equals("interval")) {
            presenceEvent.getTimestamp(); // <unix timestamp>
            presenceEvent.getOccupancy(); // <# users in channel>
            presenceEvent.getHereNowRefresh(); // true
        }
    }
});
```

When a channel is in interval mode with `presence_deltas` `pnconfig` flag enabled, the interval message may also include the following fields which contain an array of changed UUIDs since the last interval message. This settings can be altered in the [Admin Portal](https://admin.pubnub.com)

* joined
* left
* timedout

For example, this interval message indicates there were 2 new UUIDs that joined and 1 timed out UUID since the last interval:

```java
if (presence.getEvent().equals("interval")) {
    presence.getTimestamp(); // <unix timestamp>
    presence.getOccupancy(); // <# users in channel>
    presence.getJoin(); // ["uuid1", "uuid2"]
    presence.getTimeout(); // ["uuid3"]
}
```

If the full interval message is greater than `30 KB` (since the max publish payload is `∼32 KiB`), none of the extra fields will be present. Instead there will be a `here_now_refresh` boolean field set to `true`. This indicates to the user that they should do a `hereNow` request to get the complete list of users present in the channel.

```java
if (presence.getEvent().equals("interval")) {
    presence.getTimestamp(); // <unix timestamp>
    presence.getOccupancy(); // <# users in channel>
    presence.getHereNowRefresh() // true
}
```

##### Wildcard subscribe to channels

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/) (with Enable Wildcard Subscribe checked). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

Wildcard subscribes allow the client to subscribe to multiple channels using wildcard. For example, if you subscribe to `a.*` you will get all messages for `a.b`, `a.c`, `a.x`. The wildcarded `*` portion refers to any portion of the channel string name after the `dot (.)`.

```java
pubnub.subscribe()
    .channels(Arrays.asList("foo.*"))
    .execute();
```

:::note Wildcard grants and revokes
Only one level (`a.*`) of wildcarding is supported. If you grant on `*` or `a.b.*`, the grant will treat `*` or `a.b.*` as a single channel named either `*` or `a.b.*`. You can also revoke permissions from multiple channels using wildcards but only if you previously granted permissions using the same wildcards. Wildcard revokes, similarly to grants, only work one level deep, like `a.*`.
:::

##### Subscribing with state

:::warning Requires Presence
This method requires that the Presence add-on is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/). For information on how to receive presence events and what those events are, refer to [Presence Events](https://www.pubnub.com/docs/general/presence/presence-events#subscribe-to-presence-channel).
:::

:::note Required UUID
Always set the `UUID` to uniquely identify the user or device that connects to PubNub. This `UUID` should be persisted, and should remain unchanged for the lifetime of the user or the device. If you don't set the `UUID`, you won't be able to connect to PubNub.
:::

```java
class complexData {
    String fieldA;
    int fieldB;
}

pubNub.addListener(new SubscribeCallback() {
    @Override
    public void status(PubNub pubnub, PNStatus status) {
        if (status.getCategory() == PNStatusCategory.PNConnectedCategory){
            complexData data = new complexData();
            data.fieldA = "Awesome";
            data.fieldB = 10;
            pubnub.setPresenceState()
                    .channels(Arrays.asList("awesomeChannel"))
                    .channelGroups(Arrays.asList("awesomeChannelGroup"))
                    .state(data)
                    .async(result -> { /* check result */ });
        }
    }

    @Override
    public void message(PubNub pubnub, PNMessageResult message) {

    }

    @Override
    public void presence(PubNub pubnub, PNPresenceEventResult presence) {

    }
});

pubNub.subscribe().channels(Arrays.asList("awesomeChannel")).execute();
```

##### Subscribe to a channel group

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

```java
pubNub.subscribe()
        .channels(Arrays.asList("ch1", "ch2")) // subscribe to channels
        .channelGroups(Arrays.asList("cg1", "cg2")) // subscribe to channel groups
        .withTimetoken(1337L) // optional, pass a timetoken
        .withPresence() // also subscribe to related presence information
        .execute();
```

##### Subscribe to the presence channel of a channel group

:::note Requires Stream Controller and Presence add-ons
This method requires both the *Stream Controller* and *Presence* add-ons are enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

```java
pubNub.subscribe()
        .channelGroups(Arrays.asList("cg1", "cg2")) // subscribe to channel groups
        .withTimetoken(1337L) // optional, pass a timetoken
        .withPresence() // also subscribe to related presence information
        .execute();
```

## Unsubscribe (old)

:::warning Not recommended
The use of this method is discouraged. Use [Unsubscribe](#unsubscribe) instead.
:::

#### Description

This function causes the client to remove an open TCP socket to the PubNub Real-Time Network and stop listening for messages on a specified `channel` ID.

#### Method(s)

To `Unsubscribe from a channel` you can use the following method(s) in the Java SDK:

```java
pubnub.unsubscribe()
    .channels(Array)
    .channelGroups(Arrays)
    .execute();
```

| Parameter | Description |
| --- | --- |
| `channels`Type: Array | Unsubscribe from `channels`, Either `channel` ID or `channelGroup` is required |
| `channelGroups`Type: Array | Unsubscribe from `channel groups`, Either `channel` ID or `channelGroup` is required |
| `execute()` *Type: Command | Command that will `execute` `unsubscribe`. |

#### Sample code

Unsubscribe from a channel:

```java
pubNub.unsubscribe()
        .channels(Arrays.asList("my_channel"))
        .execute();
```

Unsubscribe from a channel group:

```java
pubNub.unsubscribe()
        .channelGroups(Arrays.asList("cg1", "cg2", "cg3"))
        .execute();
```

Unsubscribe from multiple channels:

```java
pubNub.unsubscribe()
        .channels(Arrays.asList("ch1", "ch2", "ch3"))
        .channelGroups(Arrays.asList("cg1", "cg2", "cg3"))
        .execute();
```

#### Returns

The `unsubscribe()` operation returns a `PNStatus` which contains the following operations:

| Method | Description |
| --- | --- |
| `getCategory()`Type: Enum of type `PNStatusCategory`. | See [Status events for the Java SDK](https://www.pubnub.com/docs/sdks/java/status-events#subscribe). |
| `isError()`Type: Boolean | Is `true` in case of an error. |

## Terms in this document

* **Channel** - A pathway for sending and receiving messages between devices, created automatically when you first use it, that can handle any number of users and messages for different communication needs, like 1-1 text chats, group conversations, and other data streaming.
* **Channel pattern** - A way to group and analyze channel data to track performance metrics like message counts and user engagement over time with PubNub Insights.
* **Entity** - A subscribable object within a PubNub SDK that allows you to perform context-specific operations.
* **Listener** - A function or objectthat reacts to events or messages, like new chat messages or connection updates, letting your app respond in real-time.
* **Signal** - A non-persistent message limited to 64 bytes designed for high-volume usecases where the the most recent data is relevant, like GPS location updates.