> ## 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 - Error Handling

> Exception hierarchy and when each exception is thrown in the Spring Boot SDK.

The SDK distinguishes between two classes of failures:

1. **Initial sync failures** - surfaced as exceptions during Spring context startup. The application context fails to start, which is the correct behaviour for a configuration source.
2. **Background sync / SSE failures** - never thrown. They are logged via `NexusLogger.error(...)`, and the existing cache continues to be served.

## Exception hierarchy

All SDK exceptions extend `NexusException`. A single `catch` clause covers everything:

```java theme={null}
import dev.westyx.nexus.NexusException;

try {
    String value = nexus.getSecret("DB_PASSWORD");
} catch (NexusException e) {
    // SDK-specific failure - handle as needed
}
```

The full hierarchy:

```
RuntimeException
└── NexusException                       (abstract base)
    ├── NexusInitException               (initial sync failure - getCause() has detail)
    ├── NexusUnauthorizedException       (HTTP 401)
    ├── NexusNotFoundException           (HTTP 404)
    ├── NexusPublicKeyException          (getSecret called with public key)
    ├── NexusSecretNotFoundException     (key absent from cache)
    ├── NexusAbAddonNotAvailableException (evaluateAB HTTP 403 - add-on inactive, v0.3.0)
    ├── NexusQuarantinedException         (sync 429 quarantine body - v0.4.0)
    └── NexusRateLimitedException         (write API 429 - v0.5.0)
```

All are unchecked (`RuntimeException` ancestors), so you don't need to declare them in `throws` clauses.

## When each is thrown

| Exception                                         | Thrown by                                                                        | When                                                                                                                                                                                                                |
| ------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NexusInitException`                              | `NexusClient.create()` (called by the auto-configuration during context startup) | The initial sync failed for any reason. `getCause()` returns the underlying exception.                                                                                                                              |
| `NexusUnauthorizedException`                      | `create()`, `sync()`                                                             | Server returned HTTP 401 - invalid API key, slug/key mismatch, or revoked key.                                                                                                                                      |
| `NexusBillingException` *(v0.2.0)*                | `create()`, `sync()`                                                             | Server returned HTTP 402 - tenant has invoices overdue by more than 14 days. Background sync halts; the cache continues to serve. Check `client.isBillingOverdue()`.                                                |
| `NexusNotFoundException`                          | `create()`, `sync()`                                                             | Server returned HTTP 404 - unknown subdomain or missing endpoint.                                                                                                                                                   |
| `NexusPublicKeyException`                         | `getSecret()`                                                                    | The client uses a public key. Thrown synchronously, before the network call.                                                                                                                                        |
| `NexusServiceKindMismatchException` *(v0.2.0)*    | `getSecret()`                                                                    | The service `kind` reported by the server is `"frontend"`. Frontend services must not hold secrets, regardless of key type.                                                                                         |
| `NexusSecretNotFoundException`                    | `getSecret()`                                                                    | The requested key is not in the cache. Has a `getSecretKey()` method returning the requested name.                                                                                                                  |
| `NexusWIFNotConfiguredException` *(v0.2.0)*       | `create()`                                                                       | `nexus.wif.enabled=true` but no usable workload-identity provider matched the running environment.                                                                                                                  |
| `NexusWIFTokenExchangeFailedException` *(v0.2.0)* | `create()`, `sync()`                                                             | `POST /v1/auth/token-exchange` returned a non-2xx response.                                                                                                                                                         |
| `NexusSessionExpiredException` *(v0.2.0)*         | `sync()`                                                                         | The WIF session JWT has expired and the SDK could not refresh it.                                                                                                                                                   |
| `NexusAbAddonNotAvailableException` *(v0.3.0)*    | `evaluateAB()`                                                                   | Server returned HTTP 403 - the AB Testing add-on is not active for the project.                                                                                                                                     |
| `NexusQuarantinedException` *(v0.4.0)*            | `sync()`                                                                         | Server returned HTTP 429 with `{"error":"quarantined",...}` - the project is quarantined until `expiresAt()`. Background sync pauses; cache continues to serve. `NexusStreamObserver.onQuarantined` is also called. |
| `NexusRateLimitedException` *(v0.5.0)*            | `setSecret()`, `deleteSecret()`, `deleteSecretVersion()`                         | Server returned HTTP 429 on a write endpoint. Check `getRetryAfter()` for the recommended retry delay.                                                                                                              |

## What is NOT thrown

* **Background syncs** - silently retried on the next TTL tick. Cache served.
* **SSE transport errors** - exponential backoff, 3-strike fallback to polling. No exception surfaces.
* **`304 Not Modified`** - treated as success; TTL reset.
* **Missing config / flag** - `getConfig` returns `Optional.empty()`; `getFlag` returns the `defaultValue`.

## Failure scenarios

### Wrong API key

The Spring context fails to start:

```
Caused by: dev.westyx.nexus.NexusInitException: initial sync failed
  Caused by: dev.westyx.nexus.NexusUnauthorizedException: nexus: unauthorized - invalid API key or endpoint mismatch
```

This is the **correct** behaviour - an application with no config has no defined behaviour and shouldn't accept traffic. Spring Boot's startup health checks (`/actuator/health`) will report DOWN.

### Network down at startup

If the Nexus backend is unreachable when the auto-configuration runs, you can implement a retry shell:

```java theme={null}
@Configuration
public class NexusOverride {

    @Bean
    public NexusClient nexusClient(NexusProperties props) {
        for (int i = 0; i < 5; i++) {
            try {
                NexusClient client = NexusClient.create(NexusConfig.builder()
                        .baseUrl(props.getBaseUrl())
                        .apiKey(props.getApiKey())
                        .build());
                client.connectStream();
                return client;
            } catch (NexusInitException e) {
                if (i == 4) throw e;
                try { Thread.sleep(1000L * (1 << i)); }
                catch (InterruptedException ignored) { Thread.currentThread().interrupt(); }
            }
        }
        throw new IllegalStateException("unreachable");
    }
}
```

### Reading a secret with a public key

```java theme={null}
try {
    String value = nexus.getSecret("DB_PASSWORD");
} catch (NexusPublicKeyException e) {
    log.error("nexus: public keys cannot read secrets - use a secret key");
}
```

This check fires synchronously, before the network call.

### SSE-specific behaviour

SSE failures **never** propagate to your code. The SDK logs them at `error` level and keeps the cache going via TTL polling.

If you need to detect "live updates are no longer available", check `nexus.isStreamConnected()` - it returns `false` when the stream falls back. A custom `HealthIndicator` is a good place to expose this:

```java theme={null}
@Component
class NexusHealth implements HealthIndicator {
    private final NexusClient nexus;
    NexusHealth(NexusClient nexus) { this.nexus = nexus; }

    @Override
    public Health health() {
        return nexus.isStreamConnected()
            ? Health.up().withDetail("stream", "connected").build()
            : Health.outOfService().withDetail("stream", "fallback").build();
    }
}
```
