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:

  1. Environment Specialization

    • Dev might use an in-memory H2 database; prod might use a cloud-hosted Postgres.

    • Allows infrastructure changes without touching core logic.

  2. Behavior Swapping

    • Swap a real payment gateway with a mock gateway in tests.

    • Replace complex external integrations with local stubs for faster local development.

  3. Deployment Flexibility

    • The same artifact (JAR/WAR) can be deployed to multiple environments by simply changing an activation flag - no rebuild required.

  4. Risk Reduction

    • Prevents accidental use of production-only beans in development/testing contexts.

    • Eliminates “wrong environment” incidents by physically excluding beans.

  5. 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.

  6. 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.

  7. 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:

  1. 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)

  2. 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.

circle-check

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 or InitializingBean 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:

Where <profile> exactly matches the profile name declared in:

  • spring.profiles.active

  • SPRING_PROFILES_ACTIVE environment variable

  • CLI 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

When 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.

  • Effect: Only available when spring.profiles.active=dev (or when dev 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

Profiles 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.

  • 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

When applied at the class level to a @Configuration class, all bean methods inside inherit that profile condition.

  • 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:

  • 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:

  • 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:

  • 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).

circle-info

No @Profile → bean is always included.

@Profile("default") → bean is included only if nothing else is active.

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 in spring.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:

  1. application.yaml (or .properties) → Always loaded first.

  2. application-<profile>.yaml → Loaded only if <profile> is active.

  3. Profile-specific files override values from the base file.

A. Example – No Active Profile

Files:

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:

  • 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

  • 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

B. Command-Line

C. JVM System Property

D. Environment Variable

E. Programmatically

F. Multi-Profile Beans

A single bean can match multiple profiles:

If either profile is active, the bean will be loaded.

G. Testing with Profiles

In tests, we can activate profiles with:

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:

Last updated