Shipeasy

Kotlin

The Shipeasy Kotlin server SDK — coroutine init, JVM/Android, local evaluation, configs, experiments, and tracking.

Production readyOn this page · 4 min readUpdated · June 18, 2026Works with · Kotlin · JVM · Android · Spring Boot

Server-side flags, configs, experiments, and tracking for Kotlin (JVM/Android-compatible). Evaluates locally against a background-polled blob — see the shared evaluation model.

Install

implementation("ai.shipeasy:shipeasy-kotlin:0.3.0")

Configure

Call configure() once at startup with the server key, plus an optional attributes transform that maps your user object onto the Shipeasy attribute map.

import ai.shipeasy.configure
import ai.shipeasy.Client

configure(
    System.getenv("SHIPEASY_SERVER_KEY"),
    attributes = { u -> mapOf("user_id" to u.id, "plan" to u.plan) },
)

Bind a client to the user

Construct a lightweight Client from your user object — the attributes transform turns it into the evaluation context, so the getters take no user argument:

val flags = Client(currentUser)

Evaluate a feature flag

flags.getFlag("new_checkout")

An optional default is returned only when the gate can't be evaluated (client not ready or gate absent) — a gate that evaluates false returns false:

flags.getFlag("new_checkout", default = true)

For the value plus the reason, use getFlagDetail — see Evaluation reasons:

val d: FlagDetail = flags.getFlagDetail("new_checkout")
d.value   // Boolean
d.reason  // one of the Reason constants

Read a dynamic config

flags.getConfig("billing_copy")
flags.getConfig("billing_copy", default = "Pay now")

Resolve an experiment

val r = flags.getExperiment("checkout_button", mapOf("color" to "blue"))
if (r.inExperiment) {
    r.group   // "treatment"
    r.params  // {color=...}
}

Track an event

flags.track("purchase", mapOf("amount" to 49))

Anonymous visitors

AnonIdFilter is a servlet Filter that mints the shared __se_anon_id cookie so logged-out traffic buckets like the browser:

@Bean
fun shipeasyAnonId() = FilterRegistrationBean(AnonIdFilter())

Client(anonUser).getFlag("new_checkout") // buckets on the cookie

jakarta.servlet-api is compileOnly. Non-servlet stacks (Ktor, http4k) use the AnonId primitives directly.

Testing

Engine.forTesting() does zero network — no key, init/track are no-ops. Seed with override* and reset with clearOverrides. Engine is AutoCloseable — wrap it in use { }. See Testing.

Engine.forTesting().use { engine ->
    engine.overrideFlag("new_checkout", true)
    engine.getFlag("new_checkout", emptyMap()) // true
    engine.clearOverrides()
}

Errors & feedback

Report a handled exception with see() so it folds into the errors primitive with a one-sentence consequence — what feature broke and how it degraded. see() rides the same configure() boot (server key); there is no separate error SDK or second key.

try {
    chargeCard(order, prices)
} catch (e: Exception) {
    Shipeasy.see(e).causesThe("checkout").to("use cached prices")
        .extras(mapOf("order_id" to order.id))
}

Use Shipeasy.seeViolation("large query") for a non-exception problem. Full grammar — consequence phrasing, control-flow exceptions, anti-patterns — is in Error reporting with see().

The in-app bug & feature report overlay is a standalone <script> tag you drop into your frontend — platform-agnostic, no server SDK required. See The devtools overlay.

Was this page helpful?
✎ Edit this page

On this page