This is a Spring Boot example demonstrating PostHog integration with product analytics, feature flags, and user identification using the server-side Java SDK.
- Product analytics: Track user events and behaviors
- User identification: Associate events with authenticated users via person properties
- Feature flags: Control feature rollouts with PostHog feature flags
- Server-side tracking: All tracking happens server-side with the
posthog-serverSDK - Single client bean: One PostHog client for the whole app, flushed on shutdown
Note on error tracking: the PostHog Java server SDK does not have automatic exception capture. This example reports failures as an explicit
error_occurredevent instead. Do not expect session replay or surveys from the server SDK either.
Copy .env.example to .env and fill in your values, then export them:
export POSTHOG_PROJECT_TOKEN=your_posthog_project_token
export POSTHOG_HOST=https://us.i.posthog.comGet your PostHog project token from your PostHog project settings.
./gradlew bootRunOpen http://localhost:8000 with your browser to see the app.
java-spring-boot/
├── build.gradle # Gradle build with the posthog-server dependency
├── settings.gradle
├── .env.example # Environment variable template
├── .gitignore
└── src/main/
├── resources/
│ ├── application.properties # Binds POSTHOG_* env vars into config
│ └── templates/ # Thymeleaf pages
│ ├── home.html # Home / login page
│ ├── burrito.html # Event tracking
│ ├── dashboard.html # Feature flag example
│ └── profile.html # Error reporting
└── java/com/posthog/example/
├── Application.java # Spring Boot entry point
├── config/
│ └── PostHogConfiguration.java # The single PostHog client bean
└── controller/
└── BurritoController.java # Events, identify, flags, errors
Create one client per process and expose it as a singleton bean. destroyMethod = "close" makes Spring flush queued events when the app shuts down.
@Bean(destroyMethod = "close")
public PostHogInterface posthog(
@Value("${posthog.project-token:}") String projectToken,
@Value("${posthog.host:https://us.i.posthog.com}") String host) {
PostHogConfig config = PostHogConfig
.builder(projectToken)
.host(host)
.build();
return PostHog.with(config);
}Read the token and host from the environment so secrets never live in source:
posthog.project-token=${POSTHOG_PROJECT_TOKEN:}
posthog.host=${POSTHOG_HOST:https://us.i.posthog.com}The server SDK identifies a user by attaching person properties to a capture with userProperty(...). The distinctId (first argument) must match the id your frontend identify call uses.
posthog.capture(
userId,
"user_logged_in",
PostHogCaptureOptions
.builder()
.userProperty("email", email)
.property("login_method", "email")
.build());posthog.capture(
userId,
"burrito_considered",
PostHogCaptureOptions
.builder()
.property("total_considerations", count)
.build());Evaluate flags once per request, then read individual flags off the snapshot. Avoid the deprecated per-flag helpers.
PostHogFeatureFlagEvaluations flags = posthog.evaluateFlags(userId);
boolean showNewFeature = flags.isEnabled("new-dashboard-feature");There is no automatic exception capture in the Java server SDK, so report failures as an explicit event:
try {
riskyOperation();
} catch (RuntimeException e) {
posthog.capture(
userId,
"error_occurred",
PostHogCaptureOptions
.builder()
.property("error_type", e.getClass().getSimpleName())
.property("error_message", e.getMessage())
.build());
}destroyMethod = "close" on the bean handles this for you at shutdown. If you ever
manage the client yourself, flush and close explicitly:
posthog.flush(); // send any remaining events
posthog.close(); // shut down the client