Shipeasy

Java

The Shipeasy Java server SDK — AutoCloseable client, servlet anon filter, local evaluation, configs, experiments, and tracking.

Production readyOn this page · 4 min readUpdated · June 18, 2026Works with · Java 11+ · Spring Boot · servlet containers

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

Install

<dependency>
  <groupId>ai.shipeasy</groupId>
  <artifactId>shipeasy</artifactId>
  <version>0.1.0</version>
</dependency>

Configure

Call Shipeasy.configure() once at startup with the server key, plus an optional attributes transform that maps your user object onto the Shipeasy attribute map. A simple Shipeasy.configure(apiKey) form is also available when you don't need a transform.

import ai.shipeasy.Shipeasy;
import ai.shipeasy.Client;
import ai.shipeasy.ExperimentResult;
import java.util.Map;

Shipeasy.configure(
    Shipeasy.options(System.getenv("SHIPEASY_SERVER_KEY"))
        .attributes(u -> Map.of("user_id", u.id(), "plan", 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:

Client flags = new Client(currentUser);

Evaluate a feature flag

boolean enabled = flags.getFlag("new_checkout");

A default overload returns the fallback only when the value can't be resolved (client not ready or gate absent) — a gate that evaluates false returns false:

boolean enabled = flags.getFlag("new_checkout", true);

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

FlagDetail d = flags.getFlagDetail("new_checkout");
d.value();   // boolean
d.reason();  // one of the FlagDetail.* constants

Read a dynamic config

Object cfg  = flags.getConfig("billing_copy");
Object copy = flags.getConfig("billing_copy", Map.of("title", "Default"));

Resolve an experiment

ExperimentResult r = flags.getExperiment("checkout_button", Map.of("color", "blue"));
if (r.inExperiment) {
    r.group();  // "treatment"
    r.params(); // {color=...}
}

Track an event

flags.track("purchase", Map.of("amount", 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
FilterRegistrationBean<AnonIdFilter> shipeasyAnonId() {
    var reg = new FilterRegistrationBean<>(new AnonIdFilter());
    reg.addUrlPatterns("/*");
    return reg;
}

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

jakarta.servlet-api is a provided dependency. Non-servlet stacks (Ktor, Javalin) 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, so a try-with-resources block stops the poll on close. See Testing.

try (Engine engine = Engine.forTesting()) {
    engine.overrideFlag("new_checkout", true);
    engine.getFlag("new_checkout", Map.of()); // 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 Shipeasy.configure() boot (server key); there is no separate error SDK or second key.

try {
    chargeCard(order, prices);
} catch (Exception e) {
    Shipeasy.see(e).causesThe("checkout").to("use cached prices")
        .extras(Map.of("order_id", 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