Spring Profiles
About
Spring Profiles provide a mechanism to separate environment-specific bean definitions and configuration logic, so our application can adapt automatically to different runtime contexts (e.g., development, testing, staging, production).
Instead of manually commenting/uncommenting configuration code for each environment, we can annotate beans or configuration classes with @Profile
so they are only loaded if the matching profile is active.
Purpose
Spring Profiles exist to isolate and control bean registration based on runtime context, enabling one application codebase to serve multiple operational needs without invasive code changes.
In modern applications, environments differ in infrastructure, performance requirements, security policies, and integration endpoints. If we tried to support all environments with a single unfiltered configuration, we would face:
Complex conditional logic scattered across the codebase
High risk of misconfiguration, especially when moving from lower to higher environments
Hard-to-reproduce bugs caused by unintended bean inclusion
Spring Profiles solve this by giving the container selective visibility into which beans should be considered at startup. The decision is made before bean instantiation, so irrelevant beans are never even created.
Key reasons profiles exist:
Environment Specialization
Dev might use an in-memory H2 database; prod might use a cloud-hosted Postgres.
Allows infrastructure changes without touching core logic.
Behavior Swapping
Swap a real payment gateway with a mock gateway in tests.
Replace complex external integrations with local stubs for faster local development.
Deployment Flexibility
The same artifact (JAR/WAR) can be deployed to multiple environments by simply changing an activation flag - no rebuild required.
Risk Reduction
Prevents accidental use of production-only beans in development/testing contexts.
Eliminates “wrong environment” incidents by physically excluding beans.
Clean Separation of Concerns
Configuration is moved out of business logic into declarative annotations or config files.
Developers no longer need to read conditional code to understand environment rules.
Scalability in Large Teams
Teams working on different modules can define profile-specific beans without interfering with each other’s configurations.
Encourages modularity in infrastructure definitions.
Runtime Adaptability
Supports complex setups like multi-cloud deployments, hybrid on-prem/cloud, or feature-flag-driven releases, where bean composition changes based on active profile combinations.
In short, the purpose of Spring Profiles is not just “different config for different environments”, but to build flexible, safe, and maintainable application configurations that scale across multiple deployment contexts - all while keeping the main codebase clean and environment-agnostic.
How It Works ?
Spring Profiles operate at the bean definition registration stage - before any beans are instantiated, but after all configuration sources have been loaded into the Environment
.
Here’s the step-by-step breakdown:
1. Profile Resolution Phase
When the Spring application starts, the container determines which profiles are active and which are default:
Sources for profile activation (checked in priority order):
Command-line arguments (
--spring.profiles.active=...
)JVM system properties (
-Dspring.profiles.active=...
)Environment variables (
SPRING_PROFILES_ACTIVE
)Application properties/YAML files
Programmatic calls (
ConfigurableEnvironment#setActiveProfiles
)
If no active profiles are explicitly set, the container:
Falls back to
spring.profiles.default
If
spring.profiles.default
is also not set, uses"default"
as the implicit profile.
2. Bean Definition Phase
Before bean instantiation:
Spring parses all
@Configuration
classes,@Component
classes, and imported configurations.Each bean definition is checked for profile metadata:
Beans with no
@Profile
→ eligible for all profiles.Beans with
@Profile
→ only eligible if at least one profile matches the currently active set.
Profile checks happen before any dependency resolution or @Autowired
injection.
3. Matching Logic
Spring uses set intersection logic:
Let A = Set of active profiles (from
Environment
)Let B = Set of profiles declared on the bean via
@Profile
If A ∩ B is non-empty → Bean is registered.
If empty → Bean definition is discarded.
4. Profile Expressions
Spring 4+ allows logical operators in @Profile
:
@Profile({"dev", "test"})
→ OR condition@Profile("dev & mysql")
→ AND condition@Profile("!prod")
→ NOT condition
Expression parsing is handled by ProfilesParser
, which creates a predicate evaluated against the active profile set.
5. Bean Registration Outcome
After profile filtering:
Only matching beans remain in the
BeanDefinitionRegistry
.Dependency injection operates only on the filtered set.
Beans excluded due to profile mismatch:
Are never instantiated
Do not participate in dependency resolution
Do not trigger
@PostConstruct
orInitializingBean
hooks
6. Special Cases
Overlapping profiles: Multiple beans for the same type may be active if profiles overlap. Use
@Primary
or@Qualifier
to resolve ambiguity.Profile-specific configuration classes: If a
@Configuration
class is annotated with@Profile
, all beans inside are subject to the same profile restriction.Default profile override: If
spring.profiles.active
is set, the default profile is ignored.
Naming Convention
The file naming must follow:
application-<profile>.yaml
application-<profile>.properties
Where <profile>
exactly matches the profile name declared in:
spring.profiles.active
SPRING_PROFILES_ACTIVE
environment variableCLI argument (
--spring.profiles.active=...
)Programmatic activation.
Declaring Profiles
In Spring, declaring a profile means associating a bean definition (or configuration class) with one or more environment identifiers so that the bean is only loaded when those profiles are active. This declaration is a filtering mechanism during the bean registration phase, not during bean execution.
When the container is starting up, it evaluates all bean definitions against the currently active profile set before even instantiating them. If the bean’s @Profile
condition is not satisfied, the bean is never registered in the ApplicationContext
. This is important because:
The bean's lifecycle methods (
@PostConstruct
,@PreDestroy
) are never called.Other beans cannot inject it because it doesn’t exist in the context.
This is more efficient than loading all beans and disabling them at runtime.
1. Applying @Profile
on Bean Classes
@Profile
on Bean ClassesWhen placed on a class that is a Spring bean (@Component
, @Service
, @Repository
, @Controller
), @Profile
tells Spring to register that bean only when the matching profile is active.
@Service
@Profile("dev")
public class DevPaymentService implements PaymentService {
public void pay() { System.out.println("Processing payment in DEV mode"); }
}
Effect: Only available when
spring.profiles.active=dev
(or whendev
is part of a multi-profile setup).If
dev
is not active, the container will not even know this bean exists.
2. Applying @Profile
on @Bean
Methods
@Profile
on @Bean
MethodsProfiles can also be declared at the method level inside a @Configuration
class.
This allows us to conditionally define beans without splitting the configuration into multiple classes.
@Configuration
public class DataSourceConfig {
@Bean
@Profile("prod")
public DataSource prodDataSource() {
return new HikariDataSource(); // connects to production DB
}
@Bean
@Profile("test")
public DataSource testDataSource() {
return new EmbeddedDatabaseBuilder().setType(H2).build(); // in-memory DB
}
}
Here, only one
DataSource
bean is created depending on the active profile.This prevents duplicate beans and ambiguity errors.
3. Applying @Profile
on Configuration Classes
@Profile
on Configuration ClassesWhen applied at the class level to a @Configuration
class, all bean methods inside inherit that profile condition.
@Configuration
@Profile("staging")
public class StagingConfiguration {
@Bean
public EmailService stagingEmailService() {
return new SmtpEmailService("staging-mail.company.com");
}
@Bean
public PaymentService stagingPaymentService() {
return new PaymentGateway("https://staging-payments.company.com");
}
}
This approach keeps environment-specific configurations grouped together.
Useful when multiple beans change together between environments.
4. Multi-Profile Declarations
We can assign multiple profiles to the same bean or configuration class:
@Component
@Profile({"qa", "staging"})
public class QAAndStagingLogger implements Logger { ... }
The bean will be active if any listed profile matches.
This is an OR condition by default.
5. Profile Expressions for Complex Conditions
Spring 4+ supports logical profile expressions:
AND:
"dev & mysql"
→ both must be active.OR:
"dev | qa"
→ either one is active.NOT:
"!prod"
→ active in all profiles except prod.
Example:
@Component
@Profile("dev & !cloud")
public class LocalDevFileStorage implements FileStorage { ... }
Bean loads only in local dev environments, not in cloud deployments.
6. Default Profile Binding
If we don’t explicitly set a profile, beans without a @Profile
annotation are considered profile-neutral - they load in all environments.
But we can explicitly bind beans to the default
profile:
@Component
@Profile("default")
public class DefaultMetricsService implements MetricsService { ... }
This is useful for having a fallback configuration when no profiles are active.
Default Profile
In Spring, the default profile is a fallback profile that is applied when no explicit profile is active.
It is internally represented as
"default"
.Beans annotated with
@Profile("default")
are only loaded if:No profile is set via
spring.profiles.active
(properties, YAML, env vars, CLI, etc.).No profile is activated programmatically.
Beans without any
@Profile
annotation are not tied to any profile and are always loaded regardless of active profile(s).
Why Have a Default Profile ?
Local development fallback: When running the app without specifying a profile, it still starts with sensible defaults.
Baseline configuration: Shared settings that don’t belong to any specific environment but are needed for the app to run.
Avoid startup failures: Ensures the application has at least one set of beans to load.
How It Works Internally ?
At startup, Spring checks for active profiles:
If none are set →
"default"
profile is automatically active.If one or more profiles are active →
"default"
profile is not added unless explicitly included inspring.profiles.active
.
The
"default"
profile is just a label like any other; it’s not special beyond the auto-activation rule.
application.yaml vs application-<profile>.yaml
Spring Boot automatically loads configuration files based on active profiles:
Loading Order:
application.yaml
(or.properties
) → Always loaded first.application-<profile>.yaml
→ Loaded only if<profile>
is active.Profile-specific files override values from the base file.
A. Example – No Active Profile
Files:
# application.yaml
spring:
datasource:
url: jdbc:h2:mem:default
# application-dev.yaml
spring:
datasource:
url: jdbc:mysql://localhost/devdb
Run without spring.profiles.active
:
Active profile →
"default"
Loaded configs:
application.yaml (H2 in-memory DB)
application-dev.yaml is ignored.
B. Example – Active Profile is dev
Run with:
java -jar app.jar --spring.profiles.active=dev
Active profile →
"dev"
Loaded configs:
application.yaml (base config)
application-dev.yaml (overrides datasource URL to MySQL)
"default"
profile beans are ignored.
C. Example – Multiple Profiles
java -jar app.jar --spring.profiles.active=dev,aws
application.yaml (base config)
application-dev.yaml (dev-specific overrides)
application-aws.yaml (AWS-specific overrides)
All beans with
@Profile("dev")
or@Profile("aws")
are eligible.
Activating Profiles
A. Properties/YAML
spring.profiles.active=dev
spring.profiles.default=default
spring:
profiles:
active: dev
default: default
B. Command-Line
java -jar app.jar --spring.profiles.active=prod
C. JVM System Property
java -Dspring.profiles.active=staging -jar app.jar
D. Environment Variable
export SPRING_PROFILES_ACTIVE=test
E. Programmatically
SpringApplication app = new SpringApplication(MyApp.class);
app.setAdditionalProfiles("dev");
app.run(args);
F. Multi-Profile Beans
A single bean can match multiple profiles:
@Profile({"qa", "staging"})
@Component
public class StagingPaymentService implements PaymentService { ... }
If either profile is active, the bean will be loaded.
G. Testing with Profiles
In tests, we can activate profiles with:
@SpringBootTest
@ActiveProfiles("test")
public class PaymentServiceTest { ... }
This ensures test beans replace production beans automatically.
H. Combining Profiles with Conditional Beans
@Profile
can be combined with:
@ConditionalOnProperty
@ConditionalOnClass
@ConditionalOnMissingBean
…for even more precise bean activation control.
Example:
@Bean
@Profile("cloud")
@ConditionalOnProperty(name = "storage.type", havingValue = "s3")
public FileStorage s3FileStorage() { ... }
Last updated