> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Spring Boot SDK - SSE Live Updates

> How SSE live propagation works in the Spring Boot SDK, including 429 handling.

The SDK can hold a persistent `GET /v1/stream` connection open in a daemon thread. Whenever the Nexus backend emits a change event (`config.updated`, `flag.toggled`, `secret.created`, `resync`, ...), the SDK triggers an immediate full sync - so the cache is updated within milliseconds of the change, instead of waiting for the next TTL tick.

## Auth

Java uses the **`X-Nexus-API-Key` header** on all endpoints, including `/stream`, via Java 11's stdlib `HttpClient`. The key is never passed as a `?key=` query parameter. (All Nexus SDKs send the key in this header - the browser SDKs do too, using fetch-based streaming rather than the `EventSource` API.)

## Enabling it

The auto-configuration calls `connectStream()` automatically after the initial sync (controlled by `nexus.stream`):

```yaml theme={null}
nexus:
  base-url: ...
  api-key:  ...
  stream:   true   # default - explicit here for clarity
```

If you want to run in pure TTL-polling mode, set:

```yaml theme={null}
nexus:
  stream: false
```

## What happens under the hood

1. The SDK opens an HTTP streaming response via `HttpClient.send(...)` with `Accept: text/event-stream` and the `X-Nexus-API-Key` header.
2. The server immediately sends a `resync` event, which causes the SDK to call `sync()` once to align with the current state.
3. The connection stays open. Every change event triggers another full sync.
4. While the stream is up, the polling TTL is bumped to 60 s as a safety net.

## Reconnection

Any transport error (server restart, TCP drop, network blip) triggers a reconnect with **exponential backoff**: 1s - 2s - 4s - 8s - 16s - 30s (capped at 30s).

After **`MAX_STREAM_ERRORS` consecutive transport errors** the SDK does not fall back permanently. Instead it sleeps according to the `sseReconnectCooldown` schedule and then retries SSE automatically.

```
nexus: SSE transport error (attempt 1/3)
nexus: SSE transport error (attempt 2/3)
nexus: SSE max errors reached - sleeping 5 min before next reconnect attempt
```

You can detect the cooldown state via `client.isStreamConnected()` - it returns `false` while the SDK is sleeping between reconnect attempts.

The reconnect cooldown schedule defaults to `[5, 10, 20, 40, 60]` minutes and can be tuned with the `sseReconnectCooldown` config option (see [Configuration](configuration)).

## `429 Too Many Requests`

Every project tier has a per-service stream connection limit:

| Tier   | Connections |
| ------ | ----------- |
| `free` | 5           |
| `xs`   | 10          |
| `s`    | 25          |
| `m`    | 50          |
| `l`    | 150         |
| `xl`   | 500         |

### Behaviour (v0.6.0+)

The SDK's 429 handling is **presence-conditional** on `Retry-After`:

* **With a parseable `Retry-After` header** - the SDK parses the value, **clamps to `[5 s, 5 min]`**, fires `observer.onFallback("rate-limited")` so dashboards can record the rate-limit episode, then `Thread.sleep`s for the clamped delay (interrupt-aware) and **continues the reconnect loop**. The 429 does **NOT** count toward `MAX_STREAM_ERRORS`.
* **Without a parseable `Retry-After` header** - the SDK fires `observer.onFallback("rate-limited")` and schedules a reconnect using the `sseReconnectCooldown` schedule (default 5 - 10 - 20 - 40 - 60 minutes). SSE is retried automatically after the cooldown; there is no permanent fallback.

No consumer-side code change is required to benefit from the new behaviour - the upgrade is purely internal to `NexusClient`. Existing `NexusStreamObserver` implementations will simply observe more `onFallback("rate-limited")` callbacks during rate-limit episodes, each followed by an automatic reconnect rather than a permanent fallback.

## Disabling SSE at runtime

```java theme={null}
@Service
public class NexusOps {

    private final NexusClient nexus;
    NexusOps(NexusClient nexus) { this.nexus = nexus; }

    public void goOffline() {
        nexus.disconnectStream();
    }

    public void goOnline() {
        nexus.connectStream();
    }
}
```

## What events trigger a sync

| Event                                                 | secret keys | public keys          |
| ----------------------------------------------------- | ----------- | -------------------- |
| `resync`                                              | yes         | yes                  |
| `config.created` / `.updated` / `.deleted`            | yes         | yes                  |
| `flag.created` / `.updated` / `.toggled` / `.deleted` | yes         | yes                  |
| `secret.created` / `.updated` / `.deleted`            | yes         | filtered server-side |

## Lifecycle

The SSE thread is bound to the `NexusClient` bean. The auto-configuration registers `destroyMethod = "close"`, so the thread is stopped cleanly when the Spring context shuts down.

## Inspecting status

```java theme={null}
boolean connected = client.isStreamConnected();
```

| Value   | Meaning                                                                                                                                                          |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `true`  | The SSE stream is up and pushing events                                                                                                                          |
| `false` | Stream is not active - either never started (`stream=false`), sleeping in the `sseReconnectCooldown` schedule before a retry, or `disconnectStream()` was called |
