Java SDK

Official Klime SDK for Java. Track events, identify users, and associate them with groups.

Installation

Add to your pom.xml:

<dependency>
    <groupId>com.klime</groupId>
    <artifactId>klime</artifactId>
    <version>1.1.0</version>
</dependency>

Or with Gradle:

implementation 'com.klime:klime:1.1.0'

Quick Start

import com.klime.KlimeClient;
import com.klime.TrackOptions;

import java.util.Map;

public class Example {
    public static void main(String[] args) {
        // Initialize the client
        KlimeClient client = KlimeClient.builder()
            .writeKey("your-write-key")
            .build();

        // Track an event
        client.track("Button Clicked", Map.of(
            "buttonName", "Sign up",
            "page", "/pricing"
        ), TrackOptions.builder()
            .userId("user_123")
            .build());

        // Identify a user
        client.identify("user_123", Map.of(
            "email", "user@example.com",
            "name", "Stefan",
            "plan", "pro"
        ));

        // Associate user with a group
        client.group("org_456", Map.of(
            "name", "Acme Inc",
            "plan", "enterprise"
        ), GroupOptions.builder()
            .userId("user_123")
            .build());

        // Graceful shutdown (flushes remaining events)
        client.shutdown().join();
    }
}

Installation Prompt

Copy and paste this prompt into Cursor, Copilot, or your favorite AI editor to integrate Klime:

Integrate Klime for customer analytics. Klime tracks user activity to identify which customers are healthy vs at risk of churning.

ANALYTICS MODES (determine which applies):
- Companies & Teams: Your customers are companies with multiple team members (SaaS, enterprise tools)
  → Use identify() + group() + track()
- Individual Customers: Your customers are individuals with private accounts (consumer apps, creator tools)
  → Use identify() + track() only (no group() needed)

KEY CONCEPTS:
- Every track() call requires either userId OR groupId (no anonymous events)
- Use groupId alone for org-level events (webhooks, cron jobs, system metrics)
- group() links a user to a company AND sets company traits (only for Companies & Teams mode)
- Order doesn't matter - events before identify/group still get attributed correctly

BEST PRACTICES:
- Initialize client ONCE at app startup (singleton or Spring bean)
- Store write key in KLIME_WRITE_KEY environment variable
- Call shutdown() on application stop to flush remaining events

Add to pom.xml:
<dependency>
    <groupId>com.klime</groupId>
    <artifactId>klime</artifactId>
    <version>1.1.0</version>
</dependency>

Or with Gradle: implementation 'com.klime:klime:1.1.0'

import com.klime.KlimeClient;
import com.klime.TrackOptions;
import com.klime.GroupOptions;

KlimeClient client = KlimeClient.builder().writeKey(System.getenv("KLIME_WRITE_KEY")).build();

// Identify users at signup/login:
client.identify("usr_abc123", Map.of("email", "jane@acme.com", "name", "Jane Smith"));

// Track key activities:
client.track("Report Generated", Map.of("report_type", "revenue"), TrackOptions.builder().userId("usr_abc123").build());
client.track("Feature Used", Map.of("feature", "export", "format", "csv"), TrackOptions.builder().userId("usr_abc123").build());
client.track("Teammate Invited", Map.of("role", "member"), TrackOptions.builder().userId("usr_abc123").build());

// If Companies & Teams mode: link user to their company and set company traits
client.group("org_456", Map.of("name", "Acme Inc", "plan", "enterprise"), GroupOptions.builder().userId("usr_abc123").build());

INTEGRATION WORKFLOW:

Phase 1: Discover
Explore the codebase to understand:
1. What framework is used? (Spring Boot, Quarkus, Micronaut, Jakarta EE, etc.)
2. Where is user identity available? (e.g., SecurityContextHolder, @AuthenticationPrincipal, Principal, JWT claims)
3. Is this Companies & Teams or Individual Customers?
   - Look for: organization, workspace, tenant, team, account models → Companies & Teams (use group())
   - No company/org concept, just individual users → Individual Customers (skip group())
4. Where do core user actions happen? (controllers, services, event handlers)
5. Is there existing analytics? (search: segment, posthog, mixpanel, amplitude, track)
Match your integration style to the framework's conventions.

Phase 2: Instrument
Add these calls using idiomatic patterns for the framework:
- Initialize client once (Spring: @Bean/@Configuration, Quarkus: @ApplicationScoped, Jakarta EE: @WebListener)
- identify() in auth/login success handler
- group() when user-org association is established (Companies & Teams mode only)
- track() for key user actions (see below)

WHAT TO TRACK:
Active engagement (primary): feature usage, resource creation, collaboration, completing flows
Session signals (secondary): login/session start, dashboard access - distinguishes "low usage" from "churned"
Do NOT track: every endpoint, health checks, actuator calls, background jobs

Phase 3: Verify
Confirm: client initialized, shutdown handled, identify/group/track calls added

Phase 4: Summarize
Report what you added:
- Files modified and what was added to each
- Events being tracked (list event names and what triggers them)
- How userId is obtained (and groupId if Companies & Teams mode)
- Any assumptions made or questions

API Reference

Initialization

KlimeClient client = KlimeClient.builder()
    .writeKey("your-write-key")           // Required
    .endpoint("https://i.klime.com")      // Optional, default shown
    .flushInterval(Duration.ofSeconds(2)) // Optional
    .maxBatchSize(20)                     // Optional, max 100
    .maxQueueSize(1000)                   // Optional
    .retryMaxAttempts(5)                  // Optional
    .retryInitialDelay(Duration.ofSeconds(1)) // Optional
    .flushOnShutdown(true)                // Optional
    .onError((error, events) -> { ... })  // Optional: callback for batch failures
    .onSuccess((response) -> { ... })     // Optional: callback for successful sends
    .build();

track(event, properties, options)

Track a user event.

// With all options
client.track("Feature Used", Map.of(
    "feature", "export",
    "format", "csv"
), TrackOptions.builder()
    .userId("user_123")
    .groupId("org_456")
    .build());

// Simple usage
client.track("Page Viewed", Map.of("page", "/home"));

identify(userId, traits, options)

Identify a user with traits.

client.identify("user_123", Map.of(
    "email", "user@example.com",
    "name", "Stefan",
    "createdAt", "2025-01-15T10:30:00Z"
));

group(groupId, traits, options)

Associate a user with a group and/or set group traits.

// Associate user with group and set traits
client.group("org_456", Map.of(
    "name", "Acme Inc",
    "plan", "enterprise",
    "employeeCount", 50
), GroupOptions.builder()
    .userId("user_123")
    .build());

// Just update group traits (no user)
client.group("org_456", Map.of("plan", "enterprise"));

flush()

Manually flush queued events immediately.

// Async flush
client.flush();

// Wait for flush to complete
client.flush().join();

shutdown()

Gracefully shutdown the client, flushing remaining events.

// Async shutdown
client.shutdown();

// Wait for shutdown to complete
client.shutdown().join();

getQueueSize()

Get the number of events currently queued.

int pending = client.getQueueSize();
System.out.println(pending + " events waiting to be sent");

Synchronous Methods

For cases where you need confirmation that events were sent (e.g., in tests, before exit), use the synchronous variants:

import com.klime.SendException;

try {
    BatchResponse response = client.trackSync("Critical Action", Map.of("key", "value"),
        TrackOptions.builder().userId("user_123").build());
    System.out.println("Sent! Accepted: " + response.getAccepted());
} catch (SendException e) {
    System.err.println("Failed: " + e.getMessage() + ", events: " + e.getEvents().size());
}

Available sync methods:

  • trackSync(event, properties, options) - Track synchronously
  • identifySync(userId, traits) - Identify synchronously
  • groupSync(groupId, traits, options) - Group synchronously

Features

  • Zero dependencies: Uses only Java standard library (Java 11+)
  • Automatic batching: Events are batched for efficient delivery
  • Retry with backoff: Transient failures are automatically retried
  • Thread-safe: Safe for concurrent use from multiple threads
  • Fire-and-forget: Non-blocking event submission
  • Graceful shutdown: JVM shutdown hook ensures events are flushed
  • CompletableFuture API: Async operations return CompletableFuture

Performance

When you call track(), identify(), or group(), the SDK:

  1. Adds the event to a thread-safe BlockingQueue (microseconds)
  2. Returns immediately without waiting for network I/O

Events are sent to Klime's servers by a background ScheduledExecutorService. This means:

  • No network blocking: HTTP requests happen asynchronously in background threads
  • No latency impact: Tracking calls add < 1ms to your request handling time
  • Automatic batching: Events are queued and sent in batches (default: every 2 seconds or 20 events)
// This returns immediately - no HTTP request is made here
client.track("Button Clicked", Map.of("button", "signup"),
    TrackOptions.builder().userId("user_123").build());

// Your code continues without waiting
return ResponseEntity.ok(Map.of("success", true));

The only blocking operations are flush().join() and shutdown().join(), which wait for all queued events to be sent. These are typically only called during graceful shutdown.

Configuration

OptionDefaultDescription
writeKey(required)Your Klime write key
endpointhttps://i.klime.comAPI endpoint URL
flushInterval2 secondsTime between automatic flushes
maxBatchSize20Max events per batch (max: 100)
maxQueueSize1000Max queued events
retryMaxAttempts5Max retry attempts
retryInitialDelay1 secondInitial retry delay
flushOnShutdowntrueAuto-flush on JVM shutdown

Logging

The SDK uses java.util.logging (JUL). Configure logging levels via your JUL configuration:

# logging.properties
com.klime.level = FINE

Or programmatically:

Logger.getLogger("com.klime").setLevel(Level.FINE);

For frameworks using SLF4J or Log4j, configure the standard JUL-to-SLF4J bridge.

Callbacks

KlimeClient client = KlimeClient.builder()
    .writeKey("your-write-key")
    .onError((error, events) -> {
        // Report to your error tracking service
        Sentry.captureException(error);
        System.err.println("Failed to send " + events.size() + " events: " + error.getMessage());
    })
    .onSuccess((response) -> {
        System.out.println("Sent " + response.getAccepted() + " events");
    })
    .build();

Error Handling

Status CodeBehavior
200Success (may contain partial failures)
400Malformed request - events dropped, no retry
401Invalid write key - events dropped, no retry
429Rate limited - retry with exponential backoff
503Service unavailable - retry with backoff

For synchronous operations, use *Sync() methods which throw SendException on failure:

import com.klime.SendException;

try {
    BatchResponse response = client.trackSync("Event", null,
        TrackOptions.builder().userId("user_123").build());
} catch (SendException e) {
    System.err.println("Failed: " + e.getMessage() + ", events: " + e.getEvents().size());
}