Java SDK

Add com.llmjury:sdk (Java 11+; the only runtime dependency is Gson — HTTP is java.net.http):

dependencies {
    implementation("com.llmjury:sdk:0.1.0")
}

Authentication

LlmjuryClient.builder() with no key reads your org's publishable key (llmj_pk_…) from the LLMJURY_API_KEY environment variable; LlmjuryClient.builder(apiKey) passes it explicitly. The key is sent as X-API-Key; the SDK defaults to the production API (LLMJURY_BASE_URL / .baseUrl(…) override for a local stack). See where keys come from.

Quickstart

// ---- setup, once at startup -------------------------------------------------
// Reads LLMJURY_API_KEY from the environment; defaults to https://api.llmjury.com.
LlmjuryClient client = LlmjuryClient.builder()
        .experiments("checkout-prompt")            // prefetch by experiment NAME
        .build();

// ---- per request ------------------------------------------------------------
// One session resolves the variant, prompt, and tracking together.
ExperimentSession s = client.session("checkout-prompt", userId);
String prompt = s.prompt(DEFAULT_PROMPT);          // in-code fallback survives an outage

String reply = s.intercept(call -> {               // latency/tokens/errors are intercepted
    Response r = llm.create("claude-haiku-4-5", prompt, userInput);
    call.response(r.text()).tokensOutput(r.outputTokens());
    return r.text();
});

s.trackOutcome("conversion", 1);                   // the ONLY explicit metric

What the client does

  • session(experiment, user) — the recommended entrypoint: an ExperimentSession bundles one user's interaction with one experiment. s.prompt(defaultPrompt) resolves the variant's prompt (your in-code default survives a full LLMJury outage), s.variables(defaults) resolves custom variables (model, temperature, …) from client memory, s.intercept(call -> …) wraps the model call so latency, tokens, and errors are recorded automatically, and s.trackOutcome(metric, value) records the business outcome — the only explicit metric.
  • getPrompt(…) / getVariables(…) — the session's building blocks, callable directly; never block, never throw, and fall back to your in-code defaults.
  • assign(experiment, user) — the low-level call: deterministic local bucketing, bit-for-bit identical to the Python and TypeScript SDKs; returns the variant key or null until the config loads — treat null as "use control".
  • track(type, payload) — enqueue-only; a daemon flusher thread batches with bounded retries, then spills to an optional FileOfflineStore (24h replay, original timestamps) or drops with a log line. Never blocks or throws into the host application.
  • Experiments are addressed by unique name or id — names resolve through the polled config and bucketing always runs on the canonical id, so both address forms assign identically.
  • Config is polled from GET /v1/config (ETag/304, 60s) with an immediate refresh on a newer ingest-ack config_version. The client is AutoCloseable — closing flushes and stops the background threads.

Full details in the SDK README.