Managing Environment-Specific Beans with Spring's @Profile Annotation
Spring's @Profile annotation provides a mechanism to conditionally register beans or configuration classes based on the active runtime environment. This enables flexible configuration managemnet across different deployment stages.
Basic Usage
On Configuration Classes
@Configuration
@Profile("development")
public class DevelopmentConfig {
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://localhost:3306/dev_db")
.username("dev_user")
.password("dev_pass")
.build();
}
}
@Configuration
@Profile("production")
public class ProductionConfig {
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://prod-cluster:3306/prod_db")
.username("prod_user")
.password("prod_pass")
.build();
}
}
On Bean Methods
@Configuration
public class AppConfiguration {
@Bean
@Profile("development")
public MessageService fakeMessageService() {
return new FakeMessageService();
}
@Bean
@Profile("production")
public MessageService realMessageService() {
return new RealMessageService();
}
}
On Component Classes
@Service
@Profile("development")
public class DevelopmentUserService implements UserService {
// Development-specific implementation
}
@Service
@Profile("production")
public class ProductionUserService implements UserService {
// Production-specific implementation
}
Activating Profiles
Using Properties Files
# application.yml
spring:
profiles:
active: development
group:
development: dev-database, dev-queue
production: prod-database, prod-queue
Using Command Line
java -jar app.jar --spring.profiles.active=production
Programmatically
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setAdditionalProfiles("development");
app.run(args);
}
}
Implementation Overview
Core Interfaces
public interface Environment extends PropertyResolver {
String[] getActiveProfiles();
String[] getDefaultProfiles();
boolean acceptsProfiles(Profiles profiles);
}
public interface ConfigurableEnvironment extends Environment {
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
}
ProfileCondition
public class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes =
metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attributes != null) {
String[] profiles = (String[]) attributes.getFirst("value");
return context.getEnvironment()
.acceptsProfiles(Profiles.of(profiles));
}
return true;
}
}
Annotation Definition
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
Advanced Techniques
Combining Profiles
@Configuration
public class CompositeProfileConfig {
@Bean
@Profile({"development", "testing"})
public DataSource testingDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("!development & !testing")
public DataSource productionDataSource() {
return new DriverManagerDataSource();
}
}
Using Profile Expressions
@Configuration
public class ExpressionProfileConfig {
@Bean
@Profile("development | testing")
public SecurityConfig devTestSecurity() {
return new LaxSecurityConfig();
}
@Bean
@Profile("production & !us")
public SecurityConfig nonUsSecurity() {
return new RegionalSecurityConfig();
}
}
Default Profile
@Configuration
public class DefaultProfileConfig {
@Bean
@Profile("default")
public DataSource fallbackDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
Practical Examples
Multi-Environment Logging
@Configuration
public class EnvironmentLoggingConfig {
@Bean
@Profile("development")
public LoggingService verboseLogger() {
return new VerboseLoggingService();
}
@Bean
@Profile("production")
public LoggingService conciseLogger() {
return new ConciseLoggingService();
}
@Bean
@Profile("development")
public CacheConfig localCache() {
return new LocalCacheConfig();
}
@Bean
@Profile("production")
public CacheConfig clusteredCache() {
return new ClusteredCacheConfig();
}
}
Testing with Profiles
@SpringBootTest
@ActiveProfiles("testing")
public class UserServiceTest {
@Autowired
private UserService service;
@Test
public void testWithTestingProfile() {
// Uses testing environment configuration
}
}
Conditional Configuration
@Configuration
public class ConditionalServiceConfig {
@Bean
@Profile("development")
@ConditionalOnProperty(name = "features.debug", havingValue = "true")
public DebugService debugService() {
return new DebugServiceImpl();
}
@Bean
@Profile("production")
@ConditionalOnClass(name = "com.amazonaws.services.s3.AmazonS3")
public StorageService cloudStorageService() {
return new S3StorageService();
}
}
The profile resolution process operates as follows:
- The Spring container initializes the
Environmentat startup. - Active profiles are determined from properties files, command-line arguments, or programmatic configuration.
- When processing
@Configurationclasses and bean definitions, theProfileConditionevaluates@Profileannotations. - Beans are created only if their associated profile matches the currently active set.
- The profile state remains constant throughout the application's lifecycle.
Best practices include:
- Define meaningful and consistent profile names.
- Keep profile granularity appropriate for your deployment needs.
- Avoid overusing profiles; prefer property-based configuration where possible.
- Provide sensible default configurations.
- Document profile usage and expected behavior.
Leveraging @Profile effectively enables robust, environment-aware conifguration, enhancing application maintainability and simplifying deployment workflows.