GET /v1/stream. When the Nexus platform publishes a change (config edit, secret rotation, flag toggle), the server sends an event and the SDK refreshes its local snapshot.
Starting the stream
connectStream() is non-blocking: it launches a coroutine on the SDK’s internal Dispatchers.IO
scope and returns immediately.
It is also idempotent - calling it while a stream is running leaves that stream in place and
logs at debug. Starting a second one would leave two streams live with the first no longer
reachable, so nothing short of close() could stop it.
On a closed client it raises NexusClosedException rather than quietly doing nothing.
Observing stream state
streamStatus is a StateFlow<StreamStatus>:
Deadlines
The idle deadline is what distinguishes “quiet” from “gone”. The backend writes an SSE
: ping
comment every 30 seconds, and the read loop consumes those comments, so any live connection
resets the timer three times over. A connection silently dropped by a load balancer produces no
bytes at all and is detected within 90 seconds instead of reading as healthy indefinitely.
Lifting the read deadline does not lift the connect deadline - they are separate settings.
Reconnecting
After a clean server close
The backend ends the stream at WIF session expiry and on every deploy, so a clean end-of-stream is the normal case rather than an edge case. The read loop treats it as a disconnect - firingobserver.onDisconnected(null), with a null cause because there was no error - and reconnects.
A connection that stayed up for 30 seconds or more counts as healthy: the failure budget and
the polling schedule both restart. A deploy therefore costs one quick reconnect rather than
pushing the client towards TTL polling.
After a transport error
- Back-off starts at 1 second and doubles: 1 s, 2 s, 4 s, …, capped at 30 seconds.
observer.onDisconnected(cause)fires with the error.observer.onReconnectAttempt(attempt)fires before each attempt (1-indexed).- After 3 consecutive failures the SDK calls
observer.onFallback("max-errors"), setsstreamStatustoFALLBACK, and waits out the next entry of thesseReconnectCooldownschedule before trying again. TTL-based background sync covers freshness meanwhile.
disconnectStream() or close() cancels
it.
An over-long SSE line (above 64 KiB) or a line truncated by a mid-message close is a transport
error and reconnects like any other.
Quarantine (HTTP 429 with a quarantine body)
A quarantine is a server-driven cooperative pause. Unlike a transport error, it does not consume the failure budget.- The
expires_atdeadline is clamped to at most 24 hours from now. observer.onQuarantined(reason, expiresAt)is called with the clamped value, andstreamStatusbecomesFALLBACK.- The stream waits until the deadline (minimum 5 seconds), then reconnects as if no failure had occurred.
Rate limit (HTTP 429 without a quarantine body)
A plain 429 means the server is temporarily rate-limiting this client.- The
Retry-Afterheader (in seconds) is used as the delay, with a 5-second floor. observer.onFallback("rate-limited")andobserver.onDisconnected(null)are called, andstreamStatusbecomesFALLBACK.- The stream reconnects after the delay. This does not consume the failure budget either.
auth_expiring event for WIF sessions
When WIF is active, the server sends an auth_expiring event before the session token expires.
The SDK intercepts it and refreshes the session in the background - it does not trigger a sync
and does not call onEvent, so your observer sees no callback for it. This gives zero-downtime
token rotation for long-running WIF sessions.
Observer callbacks are guarded
Every callback is invoked through a single guarded dispatcher, so a callback that throws is absorbed, reported through the SDK logger atwarn, and cannot stop the stream.
CancellationException is rethrown untouched, because it is how close() stops the stream. See
Stream observer.
Disconnecting the stream
streamStatus to DISCONNECTED. The HTTP client and TTL sync are
unaffected, and connectStream() starts a fresh stream afterwards.
Shutting down completely
close() is idempotent and final -
connectStream() afterwards raises NexusClosedException. There is no need to call
disconnectStream() first.
NexusClient is AutoCloseable, so use { } works:
