Java SDK

The SDK is open source: github.com/llmjury/llmjury-java (Apache-2.0 — issues and PRs welcome).

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

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

Set up your API key

The client won't build without your org's publishable key (llmj_pk_…) — grab it from the dashboard under Settings → API keys (admins can view it there any time). Then either:

Recommended — set the environment variable. The no-arg builder reads LLMJURY_API_KEY automatically, so the key never lives in your code:

export LLMJURY_API_KEY=llmj_pk_...   # shell / CI secret / deployment env / .env file
LlmjuryClient client = LlmjuryClient.builder().build();   // picks up LLMJURY_API_KEY

Or pass it explicitly if your platform injects secrets another way:

LlmjuryClient client = LlmjuryClient.builder("llmj_pk_...").build();

If neither is provided, build() throws immediately with a message saying exactly that — you can't accidentally run unauthenticated. The key is sent as X-API-Key; the SDK defaults to the production API (LLMJURY_BASE_URL / .baseUrl(…) override for a local stack).

Quickstart

// ---- setup, once at startup -------------------------------------------------
// The builder needs your publishable API key (dashboard -> Settings -> API keys).
// Easiest: `export LLMJURY_API_KEY=llmj_pk_...` — builder() reads it automatically.
// Or pass it in code: LlmjuryClient.builder("llmj_pk_...")
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.