Documentation

Presence

List connections in a segment and observe presence notifications, which are not a complete user roster.

Before you start: complete Messaging. Use its channel and chat handles, with credentials that grant read access to chat. Register listeners before you connect. Request a listing only after the channel is connected.

Presence describes connections, not unique people. A user with three connections appears three times, each with its own connectionId.

1. Register a listener and hold presence interest

Registration presenceListener =
    chat.onPresence(
        event ->
            System.out.println(
                "Joined: "
                    + event.joined()
                    + " "
                    + event.tokenReference()
                    + " "
                    + event.connectionId()));

Subscription presenceSubscription = chat.subscribePresence();

onPresence registers a local listener. subscribePresence requests notifications. Notifications arrive only while you hold a presence subscription. Keep both until this part of your application is finished. event.joined() is false when the connection left. event.timestamp() is a long in Unix milliseconds.

A presence subscription does not join the segment. It does not give you messages, and your connection does not show in the presence of that segment because of it. To receive the messages of the segment, also call subscribe(). A publish does not join the segment. A connection that only publishes does not show in the presence of that segment.

The server sends presence events as follows:

  • A connection does not get the join event that it causes. A different connection of the same token gets it.
  • When a connection unsubscribes from a segment, watchers get a leave event with the same connectionId as the join. The connection stays connected.
  • On default, a connection causes a join when it connects and a leave when it closes. The default presence list includes all connections of the channel.
  • After a reconnect, watchers get a leave for the previous connectionId and a join for the new one. The SDK subscribes again to the presence that you hold with subscribePresence.

2. Fetch a snapshot

After you connect, request the first page:

PresencePage page;

try {
  page = chat.presenceList(1, 25).join();
} catch (CompletionException failure) {
  System.out.println("Presence query failed: " + failure.getCause().getMessage());

  return;
}

System.out.println("Connections: " + page.total());
System.out.println("Page: " + page.currentPage());

for (PresenceConnection connection : page.connections()) {
  System.out.println(connection.tokenReference() + " " + connection.connectionId());
}

A query does not require a held presence subscription, but it does require an established connection and read permission.

Read the result fields by their types:

FieldJava typeMeaning
total()intReported connection count
perPage()intPage size reported by the server
currentPage()intCurrent page number
from(), to()intRange reported for this page
connections()List<PresenceConnection>Connection records, not unique users
connections().get(index).timestamp()longUnix milliseconds for that connection

3. Paginate and handle failures

page must be at least 1, and perPage must be from 1 to 100. Other values fail the future with CONFIGURATION. The SDK does not convert or clamp them.

static List<PresenceConnection> listConnections(Segment segment) {
  List<PresenceConnection> connections = new ArrayList<>();

  for (int pageNumber = 1; ; pageNumber++) {
    PresencePage page = segment.presenceList(pageNumber, 25).join();
    connections.addAll(page.connections());

    if (page.connections().isEmpty() || page.to() >= page.total()) {
      return connections;
    }
  }
}

The SDK routes a query's reply only after listeners return, so a listener that waits for presenceList always times out. To refresh a listing when presence changes, query from a thread of your own:

Registration refreshListener =
    chat.onPresence(
        event ->
            CompletableFuture.supplyAsync(() -> listConnections(chat))
                .whenComplete(
                    (connections, failure) -> {
                      if (failure != null) {
                        System.out.println(
                            "Presence query failed: " + failure.getCause().getMessage());

                        return;
                      }

                      System.out.println("Records fetched: " + connections.size());
                    }));

supplyAsync runs listConnections on the common pool, not on the client's threads. Only one query can be in flight per channel, even when the queries target different segments. A second query fails with OPERATION_IN_PROGRESS, so serialize refreshes that can overlap. A query also fails with BACKPRESSURE while the writer is full or paused after a rate limit.

A server refusal fails the future with a ServerErrorException at once, with subType() "PRES_LIST". The refusal does not also reach onError. The default deadline is ten seconds. A timeout (TIMEOUT) or a cancellation frees the query slot and does not close the connection. The SDK ignores a late reply and does not retry queries. A connection loss fails the query with TRANSPORT, so issue a new query after recovery.

Past the last page, connections() is empty and from() > to(). Do not interpret that as an error.

Listings aggregate across the cluster, but pages are not an atomic snapshot. Connections can join or leave between requests, so a multi-page result can omit or repeat records. Presence events go to watchers on all server nodes of the channel. But a connection that joined before you subscribed, or while you were disconnected, does not cause an event for you. Refresh listings when current state matters, and do not treat presence as a durable user roster.

4. Release presence resources

presenceSubscription.cancel();
refreshListener.close();
presenceListener.close();

Close the channel when its owner no longer needs it. A connection to default has automatic message membership, but still needs subscribePresence() to request presence events.

Next: Errors and troubleshooting.

We use Google cookies for analytics and for advertising measurement, only if you allow them. Until you choose, Google sets no cookies. Refer to our Cookie Policy.