Environment Profiles (@Profile)
Environment Profiles (@Profile)
Every serious application runs in more than one environment: a developer's laptop, a shared QA server, a staging environment, and production. Each of those environments typically needs different infrastructure — an in-memory H2 database locally, a real PostgreSQL instance in staging, encrypted credentials in production. Duplicating configuration classes per environment or hiding every difference behind a forest of if statements is fragile and error-prone.
Spring's profile mechanism solves this cleanly. A profile is a named logical grouping. You annotate beans or entire configuration classes with @Profile, activate one or more profiles at startup, and Spring registers only the beans whose profiles match. Everything else is ignored — safely, at the container level, not with runtime conditionals scattered through your code.
Declaring a Profile-Specific Bean
The @Profile annotation accepts a single string or an array of strings. A bean annotated with @Profile("dev") is registered only when the dev profile is active.
Spring evaluates @Profile at container startup. If no active profile matches, the bean is simply not registered. If your code injects DataSource without a qualifier and neither profile is active, you get a NoSuchBeanDefinitionException at startup — which is exactly the right failure mode: loud, early, before any request is served.
Annotating an Entire Configuration Class
When an entire class is only relevant to one environment, annotate the class itself rather than each individual @Bean method. This is cleaner and makes the intent obvious:
@Profile is on the class, every @Bean method inside inherits it. When it is on a single @Bean method, only that bean is profile-gated. Use class-level annotation when the whole configuration is environment-specific; use method-level when you are mixing shared and profile-specific beans in the same class.
Profile Expressions — Combining Profiles Logically
Since Spring 5.1, the string inside @Profile supports a simple expression language that lets you write conditions without multiple annotations:
@Profile("dev")— active whendevis active.@Profile("!prod")— active whenprodis not active (useful for safety guards).@Profile("dev | staging")— active when either is active (OR logic).@Profile("cloud & eu-west")— active only when both are active (AND logic).@Profile("(dev | staging) & !legacy")— grouped logical expressions.
@Profile("!prod") is more defensive than annotating the real implementation with @Profile("prod"). If someone runs the app without any profile the stub loads — which is far safer than accidentally loading a production integration.
Activating Profiles
Spring reads the active profiles from several sources, evaluated in priority order. The most common mechanisms are:
1. In application.properties / application.yml:
This is convenient for local defaults but should not be committed with prod — otherwise every developer who clones the repo accidentally points at production.
2. As a JVM system property (highest precedence from command line):
3. As an OS environment variable (preferred for containers and CI/CD):
Environment variables follow Spring Boot's relaxed-binding rules: dots become underscores, names are uppercased. This matches the conventions of Docker, Kubernetes, and most CI platforms, making it the idiomatic production activation mechanism.
4. Programmatically — useful in tests:
Multiple profiles can be active simultaneously. Separate them with commas in the property or environment variable, or supply multiple values to @ActiveProfiles:
Profile-Specific Property Files
Spring Boot extends the profile mechanism to property files automatically. If the active profile is staging, Boot loads both application.properties and application-staging.properties (or the YAML equivalents). The profile-specific file's values override the base file's values.
application-prod.properties in version control is the right place for non-secret production settings (log levels, endpoint exposure), but database passwords and API keys must come from environment variables or a secrets manager — even in profile-specific files.
The Default Profile
Beans annotated with @Profile("default") are registered when no profile is active. This is a useful safety net: supply a sensible fallback (usually a local stub) so the application starts cleanly even without an explicit profile, rather than throwing a missing-bean error.
Summary
The @Profile annotation gives you a first-class, Spring-managed way to vary your bean graph per environment. Annotate beans or whole configuration classes with a profile name; use expression syntax (!, |, &) for richer conditions. Activate profiles via environment variables for containers, via application.properties for developer defaults, and via @ActiveProfiles in tests. Combine @Profile with profile-specific property files (application-{profile}.properties) to keep every environment's configuration collocated, readable, and free of runtime if checks.