Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5e5e8b3
feat(core): Add `localEvaluation` API
dustinbyrne Oct 6, 2025
4c4d344
feat(core): Add an optional shutdown override to Feature Flags
dustinbyrne Oct 6, 2025
45a13fc
docs(core): Update CHANGELOG
dustinbyrne Oct 6, 2025
6d60133
feat(server): Add feature flags local evaluation
dustinbyrne Oct 6, 2025
bf16cac
docs(server): Update CHANGELOG
dustinbyrne Oct 6, 2025
f45a355
docs(server): Update USAGE
dustinbyrne Oct 6, 2025
6f3db02
chore: Update Java sample to include local eval
dustinbyrne Oct 6, 2025
9684198
Don't shadow reserved keyword identifiers
dustinbyrne Oct 8, 2025
bde72bd
Drop a list allocation
dustinbyrne Oct 8, 2025
59c2681
Add enums for property operators and types
dustinbyrne Oct 8, 2025
9ae1a8d
Regex patterns are static
dustinbyrne Oct 8, 2025
a2722a1
Separate duties in feature flags
dustinbyrne Oct 9, 2025
4e7cfc2
refactor: Move local eval models to core
dustinbyrne Oct 21, 2025
2a4e99e
refactor: Use deserialization from core
dustinbyrne Oct 21, 2025
e911234
fix: Flag definitions are loaded synchronously if missing
dustinbyrne Oct 21, 2025
4774b85
fix: Use group properties when evaluating group flags
dustinbyrne Oct 21, 2025
2195c01
test: Use JSON objects
dustinbyrne Oct 21, 2025
9f1bea1
test: Add flag dependency tests
dustinbyrne Oct 21, 2025
712eb6f
fix: Mark the poller as daemon
dustinbyrne Oct 21, 2025
27ef01b
chore(java-sample): Move logic out of onFeatureFlags
dustinbyrne Oct 21, 2025
bee6921
docs(core): Update CHANGELOG
dustinbyrne Oct 21, 2025
f7ae8a9
chore(server): apply formatter
dustinbyrne Oct 21, 2025
518dc86
fix: Properly type `userProperties`/`groupProperties`
dustinbyrne Oct 28, 2025
887a3cf
feat: Setting `personalApiKey` turns local eval on by default
dustinbyrne Oct 28, 2025
2e1d5f6
feat: `sendFeatureFlags` config is respected
dustinbyrne Oct 10, 2025
4618396
chore: Add send feature flags on capture to Java example
dustinbyrne Oct 10, 2025
d40388a
fix: `groupProperties` and `userProperties` typed correctly
dustinbyrne Oct 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package com.posthog.java.sample;

import com.posthog.server.PostHog;
import com.posthog.server.PostHogCaptureOptions;
import com.posthog.server.PostHogConfig;
import com.posthog.server.PostHog;
import com.posthog.server.PostHogFeatureFlagOptions;
import com.posthog.server.PostHogInterface;

import com.posthog.server.PostHogSendFeatureFlagOptions;
import java.util.HashMap;
import java.util.Map;

/**
* Simple Java 1.8 example demonstrating PostHog usage
*/
public class PostHogJavaExample {

public static void main(String[] args) {
PostHogConfig config = PostHogConfig
.builder("phc_wz4KZkikEluCCdfY2B2h7MXYygNGdTqFgjbU7I1ZdVR")
.builder("phc_wxtaSxv9yC8UYxUAxNojluoAf41L8p6SJZmiTMtS8jA")
.personalApiKey("phs_DuaFTmUtxQNj5R2W03emB1jMLIX5XwDvrt3DKfi5uYNcxzd")
.host("http://localhost:8010")
.localEvaluation(true)
.debug(true)
.build();

PostHogInterface postHog = PostHog.with(config);
PostHogInterface posthog = PostHog.with(config);

postHog.group("distinct-id", "company", "some-company-id");
postHog.capture(
posthog.group("distinct-id", "company", "some-company-id");
posthog.capture(
"distinct-id",
"new-purchase",
PostHogCaptureOptions
Expand All @@ -31,29 +36,54 @@ public static void main(String[] args) {

HashMap<String, Object> userProperties = new HashMap<>();
userProperties.put("email", "user@example.com");
postHog.identify("distinct-id", userProperties);
posthog.identify("distinct-id", userProperties);

// AVOID - Anonymous inner class holds reference to outer class.
// The following won't serialize properly.
// postHog.identify("user-123", new HashMap<String, Object>() {{
// put("key", "value");
// posthog.identify("user-123", new HashMap<String, Object>() {{
// put("key", "value");
// }});

postHog.alias("distinct-id", "alias-id");
posthog.alias("distinct-id", "alias-id");


if (postHog.isFeatureEnabled("distinct-id", "beta-feature", false)) {
// Feature flag examples with local evaluation
if (posthog.isFeatureEnabled("distinct-id", "beta-feature", false)) {
System.out.println("The feature is enabled.");
}

Object flagValue = postHog.getFeatureFlag("distinct-id", "multi-variate-flag", "default");
Object flagValue = posthog.getFeatureFlag("distinct-id", "multi-variate-flag", "default");
String flagVariate = flagValue instanceof String ? (String) flagValue : "default";
Object flagPayload = postHog.getFeatureFlagPayload("distinct-id", "multi-variate-flag");
Object flagPayload = posthog.getFeatureFlagPayload("distinct-id", "multi-variate-flag");

System.out.println("The flag variant was: " + flagVariate);
System.out.println("Received flag payload: " + flagPayload);

postHog.flush();
postHog.close();
Boolean hasFilePreview = posthog.isFeatureEnabled(
"distinct-id",
"file-previews",
PostHogFeatureFlagOptions
.builder()
.defaultValue(false)
.personProperty("email", "example@example.com")
.build());

System.out.println("File previews enabled: " + hasFilePreview);

posthog.capture(
"distinct-id",
"file_uploaded",
PostHogCaptureOptions
.builder()
.property("file_name", "document.pdf")
.property("file_size", 123456)
.sendFeatureFlags(PostHogSendFeatureFlagOptions
.builder()
.personProperty("email", "example@example.com")
.onlyEvaluateLocally(true)
.build())
.build());

posthog.flush();
posthog.close();
}
}
1 change: 1 addition & 0 deletions posthog-server/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Next

- fix!: Restructured `groupProperties` and `userProperties` types to match the API and other SDKs
- feat: Add local evaluation for feature flags ([#299](https://github.com/PostHog/posthog-android/issues/299))

## 1.1.0 - 2025-10-03

Expand Down
55 changes: 55 additions & 0 deletions posthog-server/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ PostHogConfig config = PostHogConfig.builder("phc_your_api_key_here")
- `flushIntervalSeconds`: Interval between automatic flushes (default: `30`)
- `featureFlagCacheSize`: The maximum number of feature flags results to cache (default: `1000`)
- `featureFlagCacheMaxAgeMs`: The maximum age of a feature flag cache record in memory in milliseconds (default: `300000` or five minutes)
- `localEvaluation`: Enable local evaluation of feature flags (default: `false`)
- `personalApiKey`: Personal API key required for local evaluation (default: `null`)
- `pollIntervalSeconds`: Interval for polling flag definitions for local evaluation (default: `30`)

## Capturing Events

Expand Down Expand Up @@ -202,6 +205,58 @@ postHog.identify("user123", userProperties, userPropertiesSetOnce);

## Feature Flags

### Local Evaluation (Experimental)

Local evaluation allows the SDK to evaluate feature flags locally without making API calls for each flag check. This reduces latency and API costs.

**How it works:**

1. The SDK periodically polls for flag definitions from PostHog (every 30 seconds by default)
2. Flags are evaluated locally using cached definitions and properties provided by the caller
3. If evaluation is inconclusive (missing properties, etc.), the SDK falls back to the API

**Requirements:**

- A feature flags secure API key _or_ a personal API key
- A feature flags secure API key can be obtained via PostHog → Settings → Project → Feature Flags → Feature Flags Secure API key
- A personal API key can be generated via PostHog → Settings → Account → Personal API Keys
- The `localEvaluation` config option set to `true`

#### Kotlin

```kotlin
val config = PostHogConfig(
apiKey = "phc_your_api_key_here",
host = "https://your-posthog-instance.com",
localEvaluation = true,
personalApiKey = "phx_your_personal_api_key_here",
pollIntervalSeconds = 30 // Optional: customize polling interval
)
```

#### Java

```java
PostHogConfig config = PostHogConfig.builder("phc_your_api_key_here")
.host("https://your-posthog-instance.com")
.localEvaluation(true)
.personalApiKey("phx_your_personal_api_key_here")
.pollIntervalSeconds(30) // Optional: customize polling interval
.build();
```

**Benefits:**

- **Reduced latency**: No API call needed for most flag evaluations
- **Lower costs**: Fewer API requests in most cases
- **Offline support**: Flags continue to work with cached definitions

**Limitations:**

- Requires person/group properties to be provided with each call
- Falls back to API for cohort-based flags without local cohort data
- May not reflect real-time flag changes (respects polling interval)

### Check if Feature is Enabled

#### Kotlin
Expand Down
Loading
Loading