A production-grade, cloud-native feature flag management platform.
FeatureMesh lets engineering teams ship code continuously and control what's visible to whom — without deployments. Define flags, target specific user segments, roll out to a percentage of traffic, track every change in an audit log, and push updates to SDK clients in real time over Kafka and SSE.
Most feature flag systems are either too simple (environment variables, config files) or too expensive (LaunchDarkly, Split). FeatureMesh is self-hosted, built on a fully reactive stack, and gives you the same core primitives used at scale:
- Targeting rules — evaluate flags against user attributes (
country,userId,plan, any context key) - Percentage rollouts — canary-deploy to N% of traffic, increase gradually
- Multi-environment support —
development,staging,productionflag states are independent - Real-time push — flag changes propagate to SDK clients via Kafka + SSE in under a second
- Audit history — every flag mutation is logged with who changed what and when
- Sub-millisecond evaluation — Redis-cached flag definitions serve hot-path evaluations without touching the database
┌──────────────────────────────────────────────────────────┐
│ FeatureMesh API :8080 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Auth │ │ Flag Admin │ │ Evaluation │ │
│ │ /auth/** │ │ /flags/** │ │ /evaluate │ │
│ └─────────────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │
│ ┌────────────┘ ┌───────────┘ │
│ ▼ ▼ │
│ ┌────────────┐ ┌───────────────┐ │
│ │ PostgreSQL │ │ Redis Cache │ │
│ │ (R2DBC) │◄────│ (L1 Store) │ │
│ └────────────┘ └───────────────┘ │
└────────────────────────────────┬─────────────────────────┘
│ flag.updated event
▼
┌────────────────┐
│ Kafka Broker │
│ (Zookeeper) │
└───────┬────────┘
│ broadcast
┌───────────┴──────────┐
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ SDK Simulator │ │ Any SDK Client │
│ :8084 (SSE) │ │ (poll or SSE) │
└─────────────────┘ └──────────────────┘
Every evaluation request goes through a two-layer resolution path:
POST /api/v1/evaluate
│
▼
Redis cache hit?
┌────────────────────────────────┐
│ YES → return cached result │ sub-millisecond
└────────────────────────────────┘
│ NO (cache miss or expired)
▼
R2DBC → PostgreSQL (non-blocking read)
│
▼
Flag enabled for this environment?
│ NO → return default value
│ YES
▼
Targeting rules match (context attributes)?
│ NO → fall through to rollout
│ YES → return targeted value
▼
Percentage rollout check (hash userId → bucket)
│
▼
Return evaluated result + write-back to Redis
Targeting rules are evaluated in priority order. The first matching rule wins. If no rule matches, the rollout percentage check determines the result.
| Concern | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.x, Spring WebFlux |
| Database | PostgreSQL + Spring Data R2DBC |
| Cache | Redis (flag definitions + evaluations) |
| Messaging | Apache Kafka + Zookeeper |
| Auth | JWT (stateless) |
| API Docs | SpringDoc OpenAPI / Swagger UI |
| Client push | SSE (Server-Sent Events) + Polling |
| Observability | Prometheus + Grafana |
| Containerization | Docker + Docker Compose |
R2DBC (Reactive Relational Database Connectivity) is used throughout — the entire read/write path to PostgreSQL is non-blocking, allowing the API to handle high evaluation throughput on a small thread pool using Project Reactor.
FeatureMesh/
├── docker/
│ └── docker-compose.yml # PostgreSQL, Redis, Kafka, Prometheus, Grafana
├── docs/
│ └── architecture.md # Sequence diagrams, ER diagram, flow details
├── featuremesh/
│ ├── featuremesh-api/ # Core platform API (port 8080)
│ │ └── src/main/java/
│ │ ├── auth/ # JWT registration + login
│ │ ├── flag/ # Flag CRUD, targeting rules, rollout config
│ │ ├── evaluation/ # Flag evaluation engine (Redis → R2DBC)
│ │ ├── audit/ # Immutable audit log
│ │ ├── telemetry/ # Evaluation metrics and telemetry
│ │ └── kafka/ # Flag change event publisher
│ └── featuremesh-sdk-simulator/ # SDK client simulator (port 8084)
│ └── src/main/java/
│ ├── polling/ # Periodic flag state polling
│ └── sse/ # SSE subscriber for real-time push
├── requests.http # Full API test suite (IntelliJ / VS Code REST Client)
└── README.md
- Docker Desktop running
- JDK 21 + Maven 3.9+ (for local dev runs)
Boot PostgreSQL, Redis, Kafka, Prometheus, and Grafana with a single command:
cd docker
docker-compose up -dServices started:
| Service | URL / Port |
|---|---|
| PostgreSQL | localhost:5432 |
| Redis | localhost:6379 |
| Kafka | localhost:9092 |
| Prometheus | localhost:9090 |
| Grafana | localhost:3000 |
cd featuremesh/featuremesh-api
../../mvnw spring-boot:runThe API starts on http://localhost:8080.
- Swagger UI:
http://localhost:8080/swagger-ui.html - OpenAPI JSON:
http://localhost:8080/v3/api-docs - Health check:
http://localhost:8080/actuator/health - Prometheus metrics:
http://localhost:8080/actuator/prometheus
In a separate terminal:
cd featuremesh/featuremesh-sdk-simulator
../../mvnw spring-boot:runThe simulator starts on http://localhost:8084. It connects to the API via SSE for real-time flag push and demonstrates the polling fallback pattern.
POST http://localhost:8080/api/v1/auth/register
Content-Type: application/json
{
"username": "developer1",
"email": "dev1@featuremesh.com",
"password": "securepassword123",
"role": "DEVELOPER"
}POST http://localhost:8080/api/v1/auth/login
Content-Type: application/json
{
"username": "developer1",
"password": "securepassword123"
}Returns { "accessToken": "eyJ..." }. Pass this as Authorization: Bearer <token> on all flag administration endpoints.
All flag endpoints require Authorization: Bearer <JWT_ACCESS_TOKEN>.
POST http://localhost:8080/api/v1/flags
Authorization: Bearer <JWT_ACCESS_TOKEN>
Content-Type: application/json
{
"key": "new-checkout",
"name": "New Checkout System",
"description": "Enables the React-based Stripe payment checkout gateway.",
"type": "BOOLEAN",
"enabled": true,
"environmentSlug": "development",
"targetingRules": [
{
"attribute": "country",
"operator": "EQUALS",
"value": "IN",
"priority": 1
}
],
"rolloutRule": {
"percentage": 50
}
}Field reference:
| Field | Description |
|---|---|
key |
Unique slug used to evaluate the flag. Immutable after creation. |
type |
BOOLEAN, STRING, NUMBER, or JSON |
environmentSlug |
Isolates flag state per environment (development, staging, production) |
targetingRules |
Ordered list of attribute-based rules. First match wins. |
rolloutRule |
Percentage of unmatched traffic that sees the flag as enabled (0–100) |
| Operator | Meaning |
|---|---|
EQUALS |
Exact match |
NOT_EQUALS |
Inverse match |
CONTAINS |
Substring / set membership |
STARTS_WITH |
String prefix match |
IN |
Value exists in a list |
This is the hot-path endpoint called by your application at runtime. No auth required — intended for server-side SDK use.
POST http://localhost:8080/api/v1/evaluate
Content-Type: application/json
{
"flagKey": "new-checkout",
"environmentSlug": "development",
"userId": "user-abc-456",
"context": {
"country": "IN"
}
}Response:
{
"flagKey": "new-checkout",
"value": true,
"reason": "TARGETING_RULE_MATCH",
"ruleId": "rule-001"
}Evaluation reasons:
| Reason | What happened |
|---|---|
TARGETING_RULE_MATCH |
A targeting rule matched the request context |
ROLLOUT_INCLUDED |
No rule matched; userId hashed into the rollout bucket |
ROLLOUT_EXCLUDED |
No rule matched; userId outside the rollout percentage |
FLAG_DISABLED |
Flag is disabled for this environment |
FLAG_NOT_FOUND |
No flag exists for this key + environment combination |
When a flag is created or updated, FeatureMesh publishes a flag.updated event to a Kafka topic. The SDK simulator subscribes and receives updates in real time via Server-Sent Events:
Admin updates flag "new-checkout" rollout from 50% → 80%
│
▼
API publishes event to Kafka topic: featuremesh.flag.updated
│
▼
SDK simulator SSE subscriber receives event
│
▼
Local flag definition cache invalidated + refreshed
│
▼
Next evaluation call uses updated rollout rule immediately
The simulator also demonstrates a polling fallback — if the SSE connection drops, it falls back to periodic polling at a configured interval, ensuring flag state remains consistent without requiring a persistent connection.
All infrastructure connection settings live in featuremesh-api/src/main/resources/application.yml:
spring:
r2dbc:
url: r2dbc:postgresql://localhost:5432/featuremeshdb
username: featuremesh
password: featuremesh
data:
redis:
host: localhost
port: 6379
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: featuremesh-group
featuremesh:
cache:
flag-ttl-seconds: 60 # Redis TTL for cached flag definitions
evaluation:
cache-results: true # Whether to cache individual evaluation resultsEach service exposes metrics via Micrometer at /actuator/prometheus. Pre-built Grafana dashboards are provisioned automatically on startup.
Key metrics tracked:
| Metric | Description |
|---|---|
featuremesh_evaluations_total |
Total evaluations by flag key and reason |
featuremesh_cache_hits_total |
Redis cache hit rate for flag definitions |
featuremesh_flag_updates_total |
Total flag mutations published to Kafka |
featuremesh_evaluation_latency |
P50/P95/P99 evaluation response time (ms) |
Access Grafana at http://localhost:3000 (default credentials: admin / admin).
cd featuremesh
./mvnw testFor a full build:
./mvnw clean compile
./mvnw testEnd-to-end API flows are covered in requests.http at the project root. Open it in IntelliJ IDEA or VS Code (REST Client extension) and run the requests sequentially: register → login → create flag → evaluate.
MIT