---
source_url: https://www.pubnub.com/docs/sdks/kotlin/api-reference/presence
title: Presence API for Kotlin SDK
updated_at: 2026-06-19T11:37:44.396Z
sdk_name: PubNub Kotlin SDK
sdk_version: 13.4.0
---

> 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


# Presence API for Kotlin SDK

PubNub Kotlin SDK, use the latest version: 13.4.0

Install:

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

:::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).
:::

Presence lets you track who is online or offline and store custom state information. Presence shows:

* When a user has joined or left a channel
* How many users are subscribed to a particular channel (occupancy)
* Which channels a user or device is subscribed to
* Presence state associated with these users

Learn more about our Presence feature in the [Presence overview](https://www.pubnub.com/docs/general/presence/overview).

:::tip Request execution
Most PubNub Kotlin SDK method invocations return an Endpoint object, which allows you to decide whether to perform the operation synchronously or asynchronously.
You must invoke the `.sync()` or `.async()` method on the Endpoint to execute the request, or the operation **will not** be performed.
```kotlin
val channel = pubnub.channel("channelName")
channel.publish("This SDK rules!").async { result ->
    result.onFailure { exception ->
        // Handle error
    }.onSuccess { value ->
        // Handle successful method result
    }
}
```
:::

## Here now

:::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).
:::

This method returns information about the current state of a channel, including a list of unique user IDs (universally unique identifiers, UUIDs) currently subscribed to the channel and the total occupancy count of the channel.

:::note Cache
This method has a 3-second response cache time.
:::

### Method(s)

To call `Here Now` you can use the following methods in the Kotlin SDK:

```kotlin
pubnub.hereNow(
    channels: List<String>,
    channelGroups: List<String>,
    includeState: Boolean,
    includeUUIDs: Boolean,
    limit: Int,
    offset: Int?
).async { result -> }
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channels | List<String> | Optional | `emptyList()` | The `channels` to get the 'here now' details of. |
| channelGroups | List<String> | Optional | `emptyList()` | The `channelGroups` to get the 'here now' details of. Wildcards are not supported. |
| includeState | Boolean | Optional | `false` | If `true`, the response will include the presence states of the users for `channels`/`channelGroups`. |
| includeUUIDs | Boolean | Optional | `true` | If true, the response will include the `UUIDs` of the connected clients. |
| limit | Int | Optional | `1000` | Maximum number of occupants to return per channel. Valid range: `0-1000`. The server enforces the maximum of `1000` and rejects values outside this range. Use `0` to get occupancy counts without user details (when `limit=0`, the server omits UUIDs entirely). |
| offset | Int? | Optional | `null` | Zero-based starting index for pagination. Returns occupants starting from this position in the list. Must be `>= 0`. Requires `limit > 0`. Server rejects negative values. Use with `limit` to paginate through large occupant lists. |

### 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.
:::

#### Get a list of UUIDs subscribed to channel

```kotlin
import com.pubnub.api.PubNub
import com.pubnub.api.UserId
import com.pubnub.api.v2.PNConfiguration

fun main() {
    println("PubNub hereNow() Example")
    println("========================")

    // Configure PubNub
    val userId = UserId("here-now-demo-user")
    val config = PNConfiguration.builder(userId, "demo").apply {
        publishKey = "demo"
    }.build()

    // Create PubNub instance
    val pubnub = PubNub.create(config)

    // Example channels to check presence for
    val channels = listOf("demo-channel-1", "demo-channel-2")

    // Basic hereNow for a single channel
    singleChannelHereNow(pubnub, channels[0])

    // hereNow for multiple channels
    multipleChannelsHereNow(pubnub, channels)

    // Advanced hereNow with additional options
    advancedHereNow(pubnub, channels[0])

    // Clean up resources
    pubnub.destroy()
}

/**
 * Demonstrates basic usage of hereNow for a single channel
 */
fun singleChannelHereNow(pubnub: PubNub, channel: String) {
    println("\n# Basic hereNow for single channel: $channel")

    pubnub.hereNow(
        channels = listOf(channel),
        limit = 100,
        offset = 10
    ).async { result ->
        result.onSuccess { response ->
            println("SUCCESS: Retrieved presence information")

            // Get information for our specific channel
            val channelData = response.channels[channel]

            if (channelData != null) {
                println("Channel: $channel")
                println("Occupancy: ${channelData.occupancy}")
                println("UUIDs: ${channelData.occupants.map { it.uuid }}")
            } else {
                println("No presence data for channel: $channel")
            }
        }.onFailure { exception ->
            println("ERROR: Failed to get presence information")
            println("Error details: ${exception.message}")
        }
    }

    // Wait for the operation to complete
    Thread.sleep(2000)
}

/**
 * Demonstrates using hereNow for multiple channels
 */
fun multipleChannelsHereNow(pubnub: PubNub, channels: List<String>) {
    println("\n# hereNow for multiple channels: $channels")

    pubnub.hereNow(
        channels = channels
    ).async { result ->
        result.onSuccess { response ->
            println("SUCCESS: Retrieved presence information for multiple channels")
            println("Total Occupancy: ${response.totalOccupancy}")
            println("Total Channels: ${response.totalChannels}")

            // Iterate through all channels in the response
            response.channels.forEach { (channelName, channelData) ->
                println("\nChannel: $channelName")
                println("Occupancy: ${channelData.occupancy}")

                if (channelData.occupants.isNotEmpty()) {
                    println("UUIDs present: ${channelData.occupants.map { it.uuid }}")
                } else {
                    println("No users present")
                }
            }
        }.onFailure { exception ->
            println("ERROR: Failed to get presence information")
            println("Error details: ${exception.message}")
        }
    }

    // Wait for the operation to complete
    Thread.sleep(2000)
}

/**
 * Demonstrates advanced usage of hereNow with additional options
 */
fun advancedHereNow(pubnub: PubNub, channel: String) {
    println("\n# Advanced hereNow with additional options: $channel")

    pubnub.hereNow(
        channels = listOf(channel),
        includeUUIDs = true, // Include the UUIDs of users
        includeState = true // Include state information for users
    ).async { result ->
        result.onSuccess { response ->
            println("SUCCESS: Retrieved detailed presence information")

            val channelData = response.channels[channel]

            if (channelData != null) {
                println("Channel: $channel")
                println("Occupancy: ${channelData.occupancy}")

                if (channelData.occupants.isNotEmpty()) {
                    println("\nPresent users:")
                    channelData.occupants.forEach { occupant ->
                        println("UUID: ${occupant.uuid}")

                        // Display state information if available
                        if (occupant.state != null) {
                            println("State: ${occupant.state}")
                        }
                    }
                } else {
                    println("No users present")
                }
            } else {
                println("No presence data for channel: $channel")
            }
        }.onFailure { exception ->
            println("ERROR: Failed to get detailed presence information")
            println("Error details: ${exception.message}")
        }
    }

    // Wait for the operation to complete
    Thread.sleep(2000)
}
```

### Returns

The `hereNow()` operation returns a `PNHereNowResult?` which contains the following operations:

| Method | Description |
| --- | --- |
| `totalChannels`Type: `Int` | Total channels |
| `totalOccupancy`Type: `Int` | Total occupancy |
| `channels`Type: `Map<String, PNHereNowChannelData>` | A map with values of `PNHereNowChannelData` for each channel. See [PNHereNowChannelData](#pnherenowchanneldata) for more details. |

#### PNHereNowChannelData

| Method | Description |
| --- | --- |
| `channelName`Type: `String` | Channel name |
| `occupancy`Type: `Int` | Occupancy of the channel |
| `occupants`Type: `List<PNHereNowOccupantData>` | A list of `PNHereNowOccupantData`, see [PNHereNowOccupantData](#pnherenowoccupantdata) for more details. |

#### PNHereNowOccupantData

| Method | Description |
| --- | --- |
| `uuid`Type: `String` | UUID of the user |
| `state`Type: `JsonElement?` | State of the user |

### Other examples

#### Returning 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).
:::

```kotlin
pubnub.hereNow(
    channels = listOf("ch1", "ch2"),
    includeState = true
).async { result: Result<PNHereNowResult> ->
    result.onSuccess { res: PNHereNowResult ->
        res.channels.values.forEach { channelData ->
            channelData.channelName // ch1
            channelData.occupancy // 3
            channelData.occupants.forEach { o ->
                o.uuid // some_uuid, returned by default
                o.state // {"data":{"isTyping":true}}, requested
            }
        }
    }.onFailure { e ->
        // handle error
        e.message
        e.statusCode
        e.pubnubError
    }
}
```

Example response:

```kotlin
.async { result: Result<PNHereNowResult> ->
    result.onSuccess { res: PNHereNowResult ->
        res.channels.values.forEach { channelData ->
            channelData.channelName // ch1
            channelData.occupancy // 3
            channelData.occupants.forEach { o ->
                o.uuid // some_uuid, returned by default
                o.state // {"data":{"isTyping":true}}, requested
            }
        }
    }.onFailure { e ->
        // handle error
        e.message
        e.statusCode
        e.pubnubError
    }
}
```

#### Return occupancy only

:::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).
:::

You can return only the `occupancy` information for a single channel by specifying the channel and setting `UUIDs` to false:

```kotlin
pubnub.hereNow(
    channels = listOf("my_channel"), // who is present on those channels?
    includeUUIDs = false, // if false, only shows occupancy count
    includeState = false // include state with request (false by default)
).async { result: Result<PNHereNowResult> ->
    result.onSuccess { res: PNHereNowResult ->
        res.channels.values.forEach { channelData ->
            channelData.channelName // ch1
            channelData.occupancy // 3
        }
    }.onFailure { e ->
        // handle error
        e.message
        e.statusCode
        e.pubnubError
    }
}
```

Example response:

```kotlin
.async { result: Result<PNHereNowResult> ->
    result.onSuccess { res: PNHereNowResult ->
        res.channels.values.forEach { channelData ->
            channelData.channelName // ch1
            channelData.occupancy // 3
        }
    }.onFailure { e ->
        // handle error
        e.message
        e.statusCode
        e.pubnubError
    }
}
```

#### Here now for channel groups

```kotlin
pubnub.hereNow(
    channelGroups = listOf("cg1", "cg2", "cg3"), // who is present on those channels groups
    includeState = true, // include state with request (false by default)
    includeUUIDs = true // if false, only shows occupancy count
).async { result: Result<PNHereNowResult> ->
    result.onSuccess { res: PNHereNowResult ->
        res.totalOccupancy
    }.onFailure { e ->
        // handle error
        e.message
        e.statusCode
        e.pubnubError
    }
}
```

Example response:

```kotlin
.async { result: Result<PNHereNowResult> ->
    result.onSuccess { res: PNHereNowResult ->
        res.totalOccupancy
    }.onFailure { e ->
        // handle error
        e.message
        e.statusCode
        e.pubnubError
    }
}
```

## Where now

:::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).
:::

This method returns the list of channels a UUID is subscribed to.

:::note Timeout events
If the app restarts (or the page refreshes) within the heartbeat window, no timeout event is generated.
:::

### Method(s)

To call `whereNow()` you can use the following method(s) in the Kotlin SDK:

```kotlin
pubnub.whereNow(
    uuid: String
).async { result -> }
```

| Parameter | Description |
| --- | --- |
| `uuid`Type: `String`Default: n/a | UUID of the user to get its current channel subscriptions. |

### Sample code

You simply need to define the `uuid` and the callback function to be used to send the data to as in the example below.

#### Get a list of channels a UUID is subscribed to

```kotlin
pubnub.whereNow()
    .async { result ->
        result.onFailure { exception ->
            // Handle error
        }.onSuccess { value ->
            // Handle successful method result
        }
    }
```

### Returns

The `whereNow()` operation returns a `PNWhereNowResult?` which contains the following operations:

| Method | Description |
| --- | --- |
| `channels`Type: `List<String>` | List of channels where the `uuid` is present. |

### Other examples

#### Obtain information about the current list of channels of some other UUID

```kotlin
pubnub.whereNow(
    uuid = "someUuid"
).async { result ->
    result.onFailure { exception ->
        // Handle error
    }.onSuccess { value ->
        // Handle successful method result
    }
}
```

## User 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).
:::

Clients can set a dynamic custom state (score, game state, location) for their users on one or more channels and store it on a channel as long as the user stays subscribed.

The state is not persisted, and when the client disconnects, the state data is lost. For more information, refer to [Presence State](https://www.pubnub.com/docs/general/presence/presence-state).

:::note Presence state format
Presence state must be expressed as a JsonObject. When calling `setState`, be sure to supply an initialized JsonObject or POJO which can be serialized to a JsonObject.
:::

### Method(s)

To set the state, call `setPresenceState()` you can use the following method(s) in the Kotlin SDK:

#### Set state

```kotlin
pubnub.setPresenceState(
    channels: List<String>,
    channelGroups: List<String>,
    state: Any,
    uuid: String
).async { result -> }
```

| Parameter | Description |
| --- | --- |
| `channels`Type: `List<String>`Default: n/a | Channels to set state to. |
| `channelGroups`Type: `List<String>`Default: n/a | Channel groups to set state to. |
| `state`Type: `Any`Default: n/a | The state to set. |
| `uuid`Type: `String`Default: n/a | The UUID to set state for. |

#### Get state

```kotlin
pubnub.getPresenceState(
    channels: List<String>,
    channelGroups: List<String>,
    uuid: String
).async { result -> }
```

To get the state, call `getPresenceState()` you can use the following method(s) in the Kotlin SDK:

| Parameter | Description |
| --- | --- |
| `channels`Type: `List<String>`Default: n/a | Channels to get state of. |
| `channelGroups`Type: `List<String>`Default: n/a | Channel groups to get state of. |
| `uuid`Type: `String`Default: n/a | The UUID to get state for. |

### Sample code

```kotlin
pubnub.setPresenceState(
    channels = listOf("my_channel"),
    state = mapOf("is_typing" to "true")
    // if no uuid supplied, own is used
).async { result -> }
pubnub.getPresenceState(
    channels = listOf("ch1", "ch2", "ch3"), // channels to fetch state for
    uuid = "such_uuid" // uuid of user to fetch, or own uuid by default
).async { result -> }
```

### Returns

The `setPresenceState()` operation returns a `PNSetStateResult?` which contains the following operations:

| Method | Description |
| --- | --- |
| `state`Type: `JsonElement` | The actual state object |

The `getPresenceState()` operation returns a `PNSetStateResult?` which contains the following operations:

| Method | Description |
| --- | --- |
| `stateByUUID`Type: `Map<String, JsonElement>` | Map of UUIDs and the user states. |

### Other examples

#### Set state for channels in channel group

```kotlin
pubnub.setPresenceState(
    channels = listOf("ch1", "ch2", "ch3"), // apply on those channels
    channelGroups = listOf("cg1", "cg2", "cg3"), // apply on those channel groups
    state = JsonObject().apply { addProperty("is_typing", true) }
).async { result ->
    result.onSuccess { res ->
        res.state // {"data":{"is_typing":true}}
    }.onFailure { e ->
        // handle error
        e.message
        e.statusCode
        e.pubnubError
    }
}
```

#### Get state for UUID

```kotlin
pubnub.getPresenceState(
    channels = listOf("ch1", "ch2"), // channels to fetch state for
    uuid = "such_uuid" // uuid of user to fetch, or own uuid by default
).async { result ->
    result.onFailure { exception ->
        exception.printStackTrace()
    }.onSuccess { value ->
        value.stateByUUID.forEach { (channel, state) ->
            println("Channel: $channel, state: $state")
        }
    }
}
```