Spring Profile

In modern software development, managing configurations across different environments—such as development, staging, and production—is a critical challenge. Hardcoding environment-specific values leads to brittle, hard-to-maintain code. Spring Profiles offer a robust, built-in mechanism within the Spring Framework to address this issue. They allow you to toggle specific Bean definitions, property files, and configuration classes based on the active environment, ensuring that your application loads only the resources relevant to its current runtime context.

Core Concepts and Mechanisms

At its core, a Spring Profile is a logical grouping of configuration elements. When a profile is activated, Spring Boot registers the Beans and properties associated with that profile into the application context. This enables environment isolation, where the same codebase can behave differently depending on the deployment target.

The @Profile Annotation

The primary tool for defining profile-specific Beans is the @Profile annotation. It can be applied to:

  • @Component classes
  • @Configuration classes
  • @Bean methods

When a Bean is annotated with @Profile("dev"), it will only be instantiated if the dev profile is active. You can specify multiple profiles in a single annotation, such as @Profile({"dev", "test"}), which means the Bean will be available in either environment.

Activating Profiles

Spring Boot provides several flexible ways to activate profiles, allowing you to choose the method that best fits your deployment strategy:

  1. JVM Arguments: Pass -Dspring.profiles.active=dev when starting the application.
  2. Environment Variables: Set SPRING_PROFILES_ACTIVE=prod in your shell or container environment.
  3. Configuration Files: Define spring.profiles.active: dev in application.yml or application.properties.
  4. Programmatic Activation: Use ConfigurableEnvironment#setActiveProfiles(...) in your application code (though this is rarely recommended for production).

Profile Hierarchy and Defaults

If no profile is explicitly activated, Spring Boot defaults to the default profile. This is useful for defining common configurations that apply universally. Additionally, you can activate multiple profiles simultaneously (e.g., dev,metrics), and Spring will merge the Bean definitions from all active profiles.

Configuration Strategies

There are two primary ways to structure profile-specific configurations: using dedicated profile-specific files or using conditional blocks within a single file.

1. Profile-Specific Files

Spring Boot automatically loads application-{profile}.yml (or .properties) files when the corresponding profile is active. For example, if the dev profile is active, Spring will load application-dev.yml in addition to the base application.yml.

# application.yml
spring:
  profiles:
    active: dev
# application-dev.yml
server:
  port: 8081
my:
  datasource:
    url: jdbc:h2:mem:devdb
# application-prod.yml
server:
  port: 80
my:
  datasource:
    url: jdbc:mysql://prod-db:3306/app

2. Conditional Blocks in application.yml

Alternatively, you can use the spring.config.activate.on-profile property to define conditional sections within a single application.yml file. This approach keeps all configuration in one place, which can be easier to manage for smaller projects.

spring:
  profiles:
    active: dev

---
spring:
  config:
    activate:
      on-profile: dev
server:
  port: 8081
my:
  datasource:
    url: jdbc:h2:mem:devdb

---
spring:
  config:
    activate:
      on-profile: prod
server:
  port: 80
my:
  datasource:
    url: jdbc:mysql://prod-db:3306/app

3. Using @Profile in Java Configuration

For complex Beans that require logic or dependency injection, using @Profile in Java configuration classes is often more appropriate.

@Configuration
public class DataSourceConfig {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:h2:mem:devdb")
                .username("sa")
                .password("")
                .driverClassName("org.h2.Driver")
                .build();
    }

    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://prod-db:3306/app")
                .username("root")
                .password("secret")
                .driverClassName("com.mysql.cj.jdbc.Driver")
                .build();
    }
}

Practical Example: Environment-Specific Logging

Logging is a common area where environment-specific configuration is needed. Developers often want verbose DEBUG logs locally, while production environments require concise WARN or ERROR logs to reduce disk I/O and noise.

# application.yml (Common)
logging:
  level:
    root: INFO

---
spring:
  config:
    activate:
      on-profile: dev
logging:
  level:
    com.example: DEBUG
  file:
    name: logs/dev.log

---
spring:
  config:
    activate:
      on-profile: prod
logging:
  level:
    com.example: WARN
  file:
    name: /var/log/app/prod.log

In this setup:

  • Dev Environment: Logs are set to DEBUG for the com.example package and written to a local file.
  • Prod Environment: Logs are restricted to WARN and above, outputting to a system directory.

Best Practices

To maintain a clean and scalable Spring application, consider the following best practices:

  • Keep Profiles Focused: Each profile should contain only the configuration that differs from the base configuration. Common settings should remain in the root application.yml.
  • Avoid Overlapping Profiles: Be cautious when activating multiple profiles. Ensure that Beans defined in different profiles do not conflict. If they do, use @Primary or @ConditionalOnMissingBean to resolve ambiguity.
  • Leverage the default Profile: Use the default profile for configurations that are universal across all environments. This minimizes duplication.
  • Centralize Activation: Avoid hardcoding active profiles in your code. Instead, manage them via environment variables or deployment scripts (e.g., Docker Compose, Kubernetes ConfigMaps). This ensures that the same artifact can be deployed to different environments without code changes.
  • Integrate with Spring Cloud Config: In microservices architectures, consider using Spring Cloud Config to manage profiles remotely. This allows for centralized, dynamic configuration management across services.

Troubleshooting Common Issues

Issue Possible Cause Solution
Bean not loaded The @Profile annotation does not match the active profile. Verify spring.profiles.active and ensure the profile name matches exactly.
Bean conflict Multiple profiles define the same Bean. Use @Primary to designate a preferred Bean, or use @ConditionalOnMissingBean to avoid overriding.
Configuration not applied Incorrect file naming or location. Ensure application-{profile}.yml is in src/main/resources and that spring.config.location is not overriding default paths.
Dynamic switch fails Beans are not refreshed after changing profiles at runtime. Runtime profile switching is not fully supported for Bean creation. Restart the application or use ApplicationContext#refresh() with caution.

Conclusion

Spring Profiles provide a powerful and flexible mechanism for managing environment-specific configurations. By combining @Profile annotations, conditional YAML blocks, and external activation methods, developers can maintain a single codebase that adapts seamlessly to different deployment targets. Adhering to best practices and understanding common pitfalls will help you build robust, maintainable Spring applications that are easy to deploy and operate.