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 questionsAPI 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 synchronouslyidentifySync(userId, traits)- Identify synchronouslygroupSync(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:
- Adds the event to a thread-safe
BlockingQueue(microseconds) - 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
| Option | Default | Description |
|---|---|---|
writeKey | (required) | Your Klime write key |
endpoint | https://i.klime.com | API endpoint URL |
flushInterval | 2 seconds | Time between automatic flushes |
maxBatchSize | 20 | Max events per batch (max: 100) |
maxQueueSize | 1000 | Max queued events |
retryMaxAttempts | 5 | Max retry attempts |
retryInitialDelay | 1 second | Initial retry delay |
flushOnShutdown | true | Auto-flush on JVM shutdown |
Logging
The SDK uses java.util.logging (JUL). Configure logging levels via your JUL configuration:
# logging.properties
com.klime.level = FINEOr 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 Code | Behavior |
|---|---|
| 200 | Success (may contain partial failures) |
| 400 | Malformed request - events dropped, no retry |
| 401 | Invalid write key - events dropped, no retry |
| 429 | Rate limited - retry with exponential backoff |
| 503 | Service 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());
}