| .devcontainer | ||
| .github | ||
| .mvn/wrapper | ||
| gradle/wrapper | ||
| k8s | ||
| src | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .gitpod.yml | ||
| build.gradle | ||
| docker-compose.yml | ||
| gradlew | ||
| gradlew.bat | ||
| LICENSE.txt | ||
| mvnw | ||
| mvnw.cmd | ||
| pom.xml | ||
| README.md | ||
| settings.gradle | ||
Prerequisites Java 17+ Docker (for PostgreSQL) Maven
-
Start PostgreSQL
docker run --name petclinic-ff
-e POSTGRES_DB=petclinic
-e POSTGRES_USER=petclinic
-e POSTGRES_PASSWORD=petclinic
-p 5432:5432
-d postgres:15 -
Run Application ./mvnw spring-boot:run -Dspring.profiles.active=postgres
URLs: App: http://localhost:8080 Flags API: http://localhost:8080/api/flags H2 Console: http://localhost:8080/h2-console Database: localhost:5432/petclinic (user: petclinic, pass: petclinic)
Feature Flags Implemented Flag Key Controls Controller Method Strategy Examples add-pet Add New Pet form PetController.initNewPetForm() Global OFF/ON, Percentage add-visit Add Visit form VisitController.processNewVisit() Percentage rollout (50%) owner-search Owner search OwnerController.processFindForm() Blacklist/Whitelist users
Behavior when OFF: Returns 403 Forbidden → redirects to /oups error page.
Feature Flag Strategies (Advanced) Strategy Configuration Logic GLOBAL {"enabled": true} Everyone ON/OFF BLACKLIST {"users":["test@example.com"]} Blocks listed users WHITELIST {"users":["admin@company.com"]} Only listed users PERCENTAGE {"percentage":25} 25% of users randomly
Feature Flag Management API
GET /api/flags # List all flags
GET /api/flags/{key} # Get specific flag
POST /api/flags # Create flag
PUT /api/flags/{key} # Update flag
DELETE /api/flags/{key} # Delete flag
Example API Calls
-
Global OFF (blocks everyone): curl -X POST http://localhost:8080/api/flags
-H "Content-Type: application/json"
-d '{ "flagKey": "add-pet", "name": "Add New Pet", "enabled": false, "strategyType": "GLOBAL" }' -
Percentage Rollout (50% users): curl -X POST http://localhost:8080/api/flags
-H "Content-Type: application/json"
-d '{ "flagKey": "add-visit", "name": "Add Visit", "enabled": true, "strategyType": "PERCENTAGE", "strategyValue": "{"percentage":50}" }' -
Blacklist specific user: curl -X POST http://localhost:8080/api/flags
-H "Content-Type: application/json"
-d '{ "flagKey": "owner-search", "name": "Owner Search", "enabled": true, "strategyType": "BLACKLIST", "strategyValue": "{"users":["test@example.com"]}" }'
Test with user email: Add ?email=test@example.com to flagged URLs.
Architecture Overview
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
│ PetClinic │◄──►│FeatureFlagService│◄──►│ PostgreSQL │
│ Controllers │ │ + Strategies │ │ feature_ │
│ │ │ │ │ flags table│
└─────────────────┘ └──────────────────┘ └─────────────┘
│ ▲
└──────────┬───────────┘
│
┌─────────────────┐
│ @FeatureFlag │ ← Custom Annotation + AOP
│ Aspect │
└─────────────────┘
Key Implementation Features Custom-built (No FF4J/Togglz) Database persistence (survives restarts) 4 Strategies: Global/Blacklist/Whitelist/Percentage Custom Annotation @FeatureFlag("add-pet") + AOP Helper function: featureFlagService.isFeatureEnabled(flagKey, userEmail) Edge cases: Fail-open, JSON parsing errors, missing flags default ON No authentication on flag API (per requirements)
Code Locations src/main/java/org/springframework/samples/petclinic/model/ ├── FeatureFlag.java # Entity
src/main/java/org/springframework/samples/petclinic/system/
├── FeatureFlag.java # @FeatureFlag annotation
src/main/java/org/springframework/samples/petclinic/service/
├── FeatureFlagService.java # Core logic + strategies
src/main/java/org/springframework/samples/petclinic/repository/
├── FeatureFlagRepository.java # JPA
src/main/java/org/springframework/samples/petclinic/controller/
├── FeatureFlagController.java # REST API
src/main/java/org/springframework/samples/petclinic/aop/
└── FeatureFlagAspect.java # AOP interceptor
Controllers with flags:
├── PetController.java # @FeatureFlag("add-pet")
├── VisitController.java # @FeatureFlag("add-visit")
└── OwnerController.java # @FeatureFlag("owner-search")
Demo Script 1. App running normally → All 3 features work 2. Create Global OFF flag → Add Pet → 403 Forbidden 3. Percentage flag → Add Visit works ~50% time (random) 4. Blacklist → Owner search blocked for test@example.com 5. Database → Flags persist after restart 6. CRUD → Create/Update/Delete via API
Troubleshooting
Issue Solution
AOP not working Add @EnableAspectJAutoProxy to PetClinicApplication.java
JSON parsing fails Check strategyValue format
Flags not persisting Verify Postgres connection
Package not found Use org.springframework.samples.petclinic.system
Original Documentation For original PetClinic features, see Spring Petclinic.
🎥 Loom Video Walkthrough: [Insert Loom Link Here]
✨ Fully functional with advanced feature flag strategies, custom annotation, and production-ready code!