The full picture
- What varies per environment: connection sizes, timeouts, log levels, external system URLs, feature flags' defaults. What must not: schema, business rules, serialization formats, security posture (staging with auth off is a fake staging).
- Profile beans, sparingly:
@Profile("local")on a stubPaymentPortbeats if-statements; but if you have profile-conditional beans everywhere, you've built N applications sharing a repo. - Profile groups (
spring.profiles.group.prod=prod,metrics,audit) compose capabilities without teams memorizing sets. - Guardrails: fail startup if no explicit profile in server environments (a naked
java -jarshould not quietly run 'default'); assert prod profile + prod DB URL agreement with a startup check — config drift between what-you-think-runs and what-runs is an incident class. - Local dev: docker-compose / Testcontainers-backed
localprofile so 'runs on my machine' is the same Postgres/Kafka as prod — profile strategy and dev-experience strategy are the same document.