com.soasap:soasap
SOASAP Kotlin SDK
Lightweight, production-ready feature flags SDK for Kotlin on the JVM and Android — lock-free O(1) reads, zero network calls on startup (unless you opt into preload), real-time SSE updates, persistent disk cache, graceful offline behavior, and minimal dependencies (OkHttp, coroutines, kotlinx.serialization).
Installation
Install from Maven Central.
Requires JDK 17+. For getJson, apply the Kotlin serialization plugin in your consumer project.
// build.gradle.kts
dependencies {
implementation("com.soasap:soasap:1.0.3")
}
<!-- pom.xml -->
<dependency>
<groupId>com.soasap</groupId>
<artifactId>soasap</artifactId>
<version>1.0.3</version>
</dependency>
Quick start
Create a SoasapClient with SoasapOptions,
set preloadFlags = true for non-blocking startup sync,
and evaluate flags with synchronous getters. Always call close() or use .use { }.
import soasap.sdk.SoasapClient
import soasap.sdk.SoasapOptions
val options = SoasapOptions("your-api-key").apply {
preloadFlags = true
onError = { ctx ->
println("[${ctx.source}] ${ctx.exception.message}")
}
}
SoasapClient(options).use { client ->
// use flags
}
Fluent builder (optional)
Optional DSL via soasap { } for the same options with a fluent API.
import soasap.sdk.soasap
soasap("your-api-key")
.preloadFlags()
.onError { ctx -> println(ctx.exception.message) }
.build()
.use { client ->
if (client.getBool("new-checkout")) {
println("New checkout enabled")
}
}
Use feature flags
Evaluate flags on the hot path with zero network or disk I/O. Getters never throw — safe for production request paths and Android UI threads.
SoasapClient(options).use { client ->
if (client.getBool("new-checkout")) {
println("New checkout enabled")
} else {
println("Old checkout")
}
}
Typed access
Bool, number, string, and JSON remote config via kotlinx.serialization.
Use @SerialName when dashboard keys differ from Kotlin property names.
val enabled = client.getBool("feature-x")
val limit = client.getNumber("rate-limit", defaultValue = 100.0)
val theme = client.getString("ui-theme", defaultValue = "light")
val config = client.getJson<CheckoutConfig>("checkout-config")
Startup sync
preloadFlags = true (recommended) starts the SSE background worker right after construction.
The constructor returns immediately; your app keeps booting even if the API is down.
Without preload, no background worker runs until the first flag read — the first evaluation may return defaults (or disk cache if present) while SSE connects in the background.
// Immediate sync (recommended)
val options = SoasapOptions("your-api-key").apply { preloadFlags = true }
// Lazy sync — first read falls back to defaults or disk cache
val options = SoasapOptions("your-api-key")
Ktor integration
Share one thread-safe SoasapClient instance across routes (for example, a DI singleton).
routing {
get("/") {
if (flags.getBool("maintenance-mode")) {
call.respond(HttpStatusCode.ServiceUnavailable, "Maintenance")
return@get
}
call.respondText("OK")
}
}
Error handling & observability
Hook background diagnostics without affecting the hot path.
Sources: Network, Disk, Parser.
SoasapOptions("your-api-key").apply {
onError = { ctx ->
println("[${ctx.source}] (transient=${ctx.isTransient}) -> ${ctx.exception.message}")
}
}
// SoasapErrorSource.Network | .Disk | .Parser
Production safety & guardrails
- No race conditions —
AtomicReferenceon immutable snapshots; atomic whole-snapshot swap - Memory cap protection (anti-DoS) — 5 MB SSE payload cap; oversized streams are dropped and reset
- Payload validation — root element must be a JSON object
{}; invalid payloads are ignored - IO coalescing (disk debounce) — disk writes coalesced at most once every ~2.5 seconds
- Thread safety — share one
SoasapClientinstance across threads
The SDK never throws from flag getters. Reconnect backoff: 1s → 2s → 5s → 10s → 30s (±200ms jitter).
Offline resiliency
| Scenario | Behavior |
|---|---|
| API unavailable | Uses stale cached flags |
| SSE disconnected | Keeps last known snapshot |
| First startup without cache | Returns default values |
| Invalid payload | Payload ignored |
| Disk cache failure | In-memory mode continues |
| Persistent network issues | Automatic reconnect with backoff |
Cache file: soasap_cache_.json (derived from API key).
Windows: %LOCALAPPDATA%\soasap\cache.
Linux/macOS: ~/.local/share/soasap/cache.
Override with cacheDirectory = "/custom/path" on SoasapOptions.
Architecture
[Hot Path] client.getBool() → AtomicReference.get(snapshot) → O(1) lookup
↑
| (atomic snapshot swap)
[Background] SSE → SseEventParser (5MB cap) → DiskWriteCoalescer (debounce) → local disk
Supported environments
- JDK 17+
- JVM backends, CLI tools, Android (OkHttp + coroutines)
- Ktor, Spring Boot, and coroutine background workers