|
| 1 | +# PostHog Spring Boot example |
| 2 | + |
| 3 | +This is a [Spring Boot](https://spring.io/projects/spring-boot) example demonstrating PostHog integration with product analytics, feature flags, and user identification using the server-side Java SDK. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Product analytics**: Track user events and behaviors |
| 8 | +- **User identification**: Associate events with authenticated users via person properties |
| 9 | +- **Feature flags**: Control feature rollouts with PostHog feature flags |
| 10 | +- **Server-side tracking**: All tracking happens server-side with the `posthog-server` SDK |
| 11 | +- **Single client bean**: One PostHog client for the whole app, flushed on shutdown |
| 12 | + |
| 13 | +> **Note on error tracking:** the PostHog Java server SDK does not have automatic |
| 14 | +> exception capture. This example reports failures as an explicit `error_occurred` |
| 15 | +> event instead. Do not expect session replay or surveys from the server SDK either. |
| 16 | +
|
| 17 | +## Getting started |
| 18 | + |
| 19 | +### 1. Configure environment variables |
| 20 | + |
| 21 | +Copy `.env.example` to `.env` and fill in your values, then export them: |
| 22 | + |
| 23 | +```bash |
| 24 | +export POSTHOG_PROJECT_TOKEN=your_posthog_project_token |
| 25 | +export POSTHOG_HOST=https://us.i.posthog.com |
| 26 | +``` |
| 27 | + |
| 28 | +Get your PostHog project token from your [PostHog project settings](https://app.posthog.com/project/settings). |
| 29 | + |
| 30 | +### 2. Run the app |
| 31 | + |
| 32 | +```bash |
| 33 | +./gradlew bootRun |
| 34 | +``` |
| 35 | + |
| 36 | +Open [http://localhost:8000](http://localhost:8000) with your browser to see the app. |
| 37 | + |
| 38 | +## Project structure |
| 39 | + |
| 40 | +``` |
| 41 | +java-spring-boot/ |
| 42 | +├── build.gradle # Gradle build with the posthog-server dependency |
| 43 | +├── settings.gradle |
| 44 | +├── .env.example # Environment variable template |
| 45 | +├── .gitignore |
| 46 | +└── src/main/ |
| 47 | + ├── resources/ |
| 48 | + │ ├── application.properties # Binds POSTHOG_* env vars into config |
| 49 | + │ └── templates/ # Thymeleaf pages |
| 50 | + │ ├── home.html # Home / login page |
| 51 | + │ ├── burrito.html # Event tracking |
| 52 | + │ ├── dashboard.html # Feature flag example |
| 53 | + │ └── profile.html # Error reporting |
| 54 | + └── java/com/posthog/example/ |
| 55 | + ├── Application.java # Spring Boot entry point |
| 56 | + ├── config/ |
| 57 | + │ └── PostHogConfiguration.java # The single PostHog client bean |
| 58 | + └── controller/ |
| 59 | + └── BurritoController.java # Events, identify, flags, errors |
| 60 | +``` |
| 61 | + |
| 62 | +## Key integration points |
| 63 | + |
| 64 | +### PostHog client bean (config/PostHogConfiguration.java) |
| 65 | + |
| 66 | +Create **one client per process** and expose it as a singleton bean. `destroyMethod = "close"` makes Spring flush queued events when the app shuts down. |
| 67 | + |
| 68 | +```java |
| 69 | +@Bean(destroyMethod = "close") |
| 70 | +public PostHogInterface posthog( |
| 71 | + @Value("${posthog.project-token:}") String projectToken, |
| 72 | + @Value("${posthog.host:https://us.i.posthog.com}") String host) { |
| 73 | + |
| 74 | + PostHogConfig config = PostHogConfig |
| 75 | + .builder(projectToken) |
| 76 | + .host(host) |
| 77 | + .build(); |
| 78 | + |
| 79 | + return PostHog.with(config); |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +### Configuration from the environment (application.properties) |
| 84 | + |
| 85 | +Read the token and host from the environment so secrets never live in source: |
| 86 | + |
| 87 | +```properties |
| 88 | +posthog.project-token=${POSTHOG_PROJECT_TOKEN:} |
| 89 | +posthog.host=${POSTHOG_HOST:https://us.i.posthog.com} |
| 90 | +``` |
| 91 | + |
| 92 | +### User identification (controller/BurritoController.java) |
| 93 | + |
| 94 | +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. |
| 95 | + |
| 96 | +```java |
| 97 | +posthog.capture( |
| 98 | + userId, |
| 99 | + "user_logged_in", |
| 100 | + PostHogCaptureOptions |
| 101 | + .builder() |
| 102 | + .userProperty("email", email) |
| 103 | + .property("login_method", "email") |
| 104 | + .build()); |
| 105 | +``` |
| 106 | + |
| 107 | +### Event tracking (controller/BurritoController.java) |
| 108 | + |
| 109 | +```java |
| 110 | +posthog.capture( |
| 111 | + userId, |
| 112 | + "burrito_considered", |
| 113 | + PostHogCaptureOptions |
| 114 | + .builder() |
| 115 | + .property("total_considerations", count) |
| 116 | + .build()); |
| 117 | +``` |
| 118 | + |
| 119 | +### Feature flags (controller/BurritoController.java) |
| 120 | + |
| 121 | +Evaluate flags once per request, then read individual flags off the snapshot. Avoid the deprecated per-flag helpers. |
| 122 | + |
| 123 | +```java |
| 124 | +PostHogFeatureFlagEvaluations flags = posthog.evaluateFlags(userId); |
| 125 | +boolean showNewFeature = flags.isEnabled("new-dashboard-feature"); |
| 126 | +``` |
| 127 | + |
| 128 | +### Error reporting (controller/BurritoController.java) |
| 129 | + |
| 130 | +There is no automatic exception capture in the Java server SDK, so report failures as an explicit event: |
| 131 | + |
| 132 | +```java |
| 133 | +try { |
| 134 | + riskyOperation(); |
| 135 | +} catch (RuntimeException e) { |
| 136 | + posthog.capture( |
| 137 | + userId, |
| 138 | + "error_occurred", |
| 139 | + PostHogCaptureOptions |
| 140 | + .builder() |
| 141 | + .property("error_type", e.getClass().getSimpleName()) |
| 142 | + .property("error_message", e.getMessage()) |
| 143 | + .build()); |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +### Flush and shutdown |
| 148 | + |
| 149 | +`destroyMethod = "close"` on the bean handles this for you at shutdown. If you ever |
| 150 | +manage the client yourself, flush and close explicitly: |
| 151 | + |
| 152 | +```java |
| 153 | +posthog.flush(); // send any remaining events |
| 154 | +posthog.close(); // shut down the client |
| 155 | +``` |
| 156 | + |
| 157 | +## Learn more |
| 158 | + |
| 159 | +- [PostHog Java integration](https://posthog.com/docs/libraries/java) |
| 160 | +- [PostHog feature flags](https://posthog.com/docs/feature-flags) |
| 161 | +- [PostHog documentation](https://posthog.com/docs) |
| 162 | +- [Spring Boot documentation](https://spring.io/projects/spring-boot) |
0 commit comments