TTL-based background sync
Every getter (getString, getBoolean, getInt, getDouble, getJson, getFlag, getSecret, getSecretFilePath) calls ensureFresh() before reading the snapshot.
ensureFresh() logic:
- If the billing-overdue flag is set, return immediately (no sync).
- If a quarantine is active (
now < quarantineUntil), return immediately (no sync). - If
now - snapshot.syncedAt < ttl, the snapshot is fresh - return immediately. - Otherwise, launch a background coroutine that calls
sync(). The getter returns the current (stale) snapshot value without waiting.
- Getters are always non-blocking.
- The first call after TTL expiry returns a slightly stale value while the refresh runs in the background.
- The second call (after the background sync completes) returns the fresh value.
ETag / 304 support
Everysync() call sends the last received ETag in an If-None-Match header. When the server data has not changed since the last sync, the server returns HTTP 304 Not Modified with an empty body. The SDK skips deserialization and keeps the existing snapshot. This reduces both bandwidth and CPU usage on polling-heavy deployments.
Stream path bypasses TTL
WhenconnectStream() is active and the server pushes an event, sync() is called immediately - the TTL is ignored. This means the snapshot is updated within milliseconds of a change on the platform, regardless of the configured ttl value.
For production services with the stream active, a large ttl (120 seconds or more) is fine. The stream handles freshness; the TTL is only a safety net for when the stream is in fallback mode.
Quarantine pause
Whensync() receives HTTP 429 with a quarantine body, the SDK:
- Sets
quarantineUntilto theexpiresAttimestamp from the response. - Calls
observer.onQuarantined(reason, expiresAt). - Throws
NexusQuarantinedException.
now < quarantineUntil, ensureFresh() skips the sync check entirely. Getters continue to return the last cached values. No sync requests are sent to the server.
After quarantineUntil passes, ensureFresh() resumes normal TTL checks and the next stale getter call triggers a fresh sync.
Billing-overdue pause
Whensync() receives HTTP 402, the SDK:
- Sets the internal
billingOverdueflag totrue. - Calls
observer.onBillingOverdue(). - Throws
NexusBillingException.
billingOverdue is true, ensureFresh() returns immediately without launching any background sync. Getters return the last successfully cached values indefinitely.
The flag is cleared on the next successful sync.
Manual sync
You can trigger a sync at any time:Snapshot atomicity
The snapshot is stored in anAtomicReference<Snapshot>. Reads and writes are lock-free. A new Snapshot object is constructed from the full sync response and swapped in atomically. There is no partial-update window where a getter could read a mix of old and new values.
File secrets
Secrets of typefile are materialised to a temporary directory at sync time. The file path is stored in fileSecretPaths and returned by getSecretFilePath(key). Files are updated on each sync when their value changes. All temp files are deleted when close() is called.
