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
connectionIdas the join. The connection stays connected. - On
default, a connection causes a join when it connects and a leave when it closes. Thedefaultpresence list includes all connections of the channel. - After a reconnect, watchers get a leave for the previous
connectionIdand a join for the new one. The SDK subscribes again to the presence that you hold withsubscribePresence.
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:
| Field | Java type | Meaning |
|---|---|---|
total() | int | Reported connection count |
perPage() | int | Page size reported by the server |
currentPage() | int | Current page number |
from(), to() | int | Range reported for this page |
connections() | List<PresenceConnection> | Connection records, not unique users |
connections().get(index).timestamp() | long | Unix 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.