Spring Profiles
Introduction
When developing applications, you often need different configurations for various environments like development, testing, and production. You might also want to enable or disable certain features based on specific conditions. Spring Profiles provide an elegant solution to manage these variations in your Spring applications.
Spring Profiles allow you to register beans and configure your application differently depending on the active profile. This means you can define environment-specific beans and properties, making your application flexible and adaptable to different scenarios without changing the code.
Understanding Spring Profiles
What are Spring Profiles?
Spring Profiles are a core feature of the Spring Framework that enables conditional bean registration and property configuration. With profiles, you can create beans that are only available when specific profiles are active.
Think of profiles as labels or tags that you apply to beans or configuration classes. When a profile is active, Spring will only register the beans tagged with that profile (or without any specific profile).
Why Use Spring Profiles?
Profiles solve several common challenges in application development:
- Environment-specific configuration: Database connections, service endpoints, and credentials typically differ between development, testing, and production environments.
- Feature toggling: Enabling or disabling features without code changes.
- Testing: Creating specific beans for testing scenarios without affecting the regular application behavior.
- Deployment flexibility: Configuring applications differently based on where they are deployed (cloud, on-premise, etc.).
Setting Up Spring Profiles
Defining Profiles in Configuration Classes
You can use the @Profile
annotation to specify which profile a configuration class or a bean belongs to:
@Configuration
@Profile("development")
public class DevelopmentConfig {
@Bean
public DataSource dataSource() {
// Return a development data source (e.g., H2 in-memory database)
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Configuration
@Profile("production")
public class ProductionConfig {
@Bean
public DataSource dataSource() {
// Return a production data source (e.g., connection to MySQL)
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://production-server:3306/myapp");
dataSource.setUsername("dbuser");
dataSource.setPassword("dbpass");
return dataSource;
}
}
Defining Profiles for Individual Beans
You can also apply the @Profile
annotation to individual bean methods:
@Configuration
public class AppConfig {
@Bean
@Profile("development")
public EmailService mockEmailService() {
return new MockEmailService();
}
@Bean
@Profile("production")
public EmailService realEmailService() {
return new SmtpEmailService();
}
}
Profile Expressions
Spring Profiles support logical expressions, giving you more flexibility:
// Active when "development" profile is active
@Profile("development")
// Active when "development" profile is NOT active
@Profile("!development")
// Active when either "development" or "testing" is active
@Profile({"development", "testing"})
// More complex expressions
@Profile("production & cloud") // Both must be active
@Profile("production | eu-region") // Either one can be active
Activating Spring Profiles
There are several ways to activate profiles in your Spring application:
In application.properties
or application.yml
# application.properties
spring.profiles.active=development,local
Or in YAML format:
# application.yml
spring:
profiles:
active: development,local
Using Environment Variables
export SPRING_PROFILES_ACTIVE=production
Using JVM System Properties
java -jar -Dspring.profiles.active=production myapp.jar
Programmatically
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setAdditionalProfiles("development");
application.run(args);
}
}
Profile-Specific Properties Files
Spring Boot supports profile-specific properties files to configure properties differently per environment:
application-{profile}.properties
orapplication-{profile}.yml
For example:
application.properties
: Common properties for all profilesapplication-development.properties
: Development-specific propertiesapplication-production.properties
: Production-specific properties
When the "development" profile is active, Spring Boot will load both application.properties
and application-development.properties
, with the latter overriding any duplicate properties.
Example:
# application.properties
app.name=My Application
logging.level.root=INFO
# application-development.properties
server.port=8080
logging.level.com.myapp=DEBUG
# application-production.properties
server.port=80
logging.level.com.myapp=WARN
Default Profile
Spring has a default profile named "default" that's active when no other profiles are explicitly activated. You can define beans for this profile:
@Profile("default")
@Bean
public DataSource defaultDataSource() {
// This bean is only created when no specific profile is active
}
Practical Example: Multi-Environment Application
Let's build a simple application that behaves differently based on the active profile:
Example Project Structure
src/main/java/com/example/demo/
├── DemoApplication.java
├── config/
│ ├── DevConfig.java
│ ├── ProdConfig.java
│ └── TestConfig.java
├── service/
│ ├── MessageService.java
│ ├── DevMessageService.java
│ ├── ProdMessageService.java
│ └── TestMessageService.java
└── controller/
└── MessageController.java
Service Interface
public interface MessageService {
String getMessage();
String getEnvironmentName();
}
Environment-specific Implementations
@Service
@Profile("dev")
public class DevMessageService implements MessageService {
@Override
public String getMessage() {
return "This is the development message service";
}
@Override
public String getEnvironmentName() {
return "Development";
}
}
@Service
@Profile("prod")
public class ProdMessageService implements MessageService {
@Override
public String getMessage() {
return "This is the production message service";
}
@Override
public String getEnvironmentName() {
return "Production";
}
}
@Service
@Profile("test")
public class TestMessageService implements MessageService {
@Override
public String getMessage() {
return "This is the test message service";
}
@Override
public String getEnvironmentName() {
return "Testing";
}
}
Controller
@RestController
public class MessageController {
private final MessageService messageService;
@Autowired
public MessageController(MessageService messageService) {
this.messageService = messageService;
}
@GetMapping("/message")
public String getMessage() {
return messageService.getMessage();
}
@GetMapping("/environment")
public String getEnvironment() {
return "Current environment: " + messageService.getEnvironmentName();
}
}
Configuration for Different Environments
@Configuration
@Profile("dev")
public class DevConfig {
@Bean
public String environmentName() {
return "Development Environment";
}
@Bean
public boolean debugEnabled() {
return true;
}
}
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
public String environmentName() {
return "Production Environment";
}
@Bean
public boolean debugEnabled() {
return false;
}
}
Properties Files
# application.properties
app.name=Profile Demo App
server.port=8080
# application-dev.properties
logging.level.com.example=DEBUG
app.description=Running in Development Mode
# application-prod.properties
logging.level.com.example=WARN
app.description=Running in Production Mode
Running the Application
To run with the "dev" profile:
java -jar -Dspring.profiles.active=dev demo.jar
Output when accessing /environment
:
Current environment: Development
To run with the "prod" profile:
java -jar -Dspring.profiles.active=prod demo.jar
Output when accessing /environment
:
Current environment: Production
Best Practices for Spring Profiles
-
Use meaningful profile names: Choose descriptive names like "development", "testing", "production" rather than abbreviations.
-
Keep a default configuration: Provide sensible defaults for when no specific profile is activated.
-
Don't hardcode profile names: Consider using constants for profile names to avoid typos and make refactoring easier.
-
Define a clear profile strategy: Document which profiles are available and when they should be used.
-
Be cautious with profile-specific beans: When two active profiles both define the same bean, you might encounter conflicts.
-
Use profile groups (Spring Boot 2.4+): You can define profile groups to activate multiple profiles at once:
spring:
profiles:
group:
development: dev,local,debug
production: prod,cloud,no-debug
- Consider using
@Conditional
for more complex cases: For advanced conditions beyond simple profiles, Spring's@Conditional
annotations provide more flexibility.
Common Pitfalls and Solutions
Mismatched Profile Names
Problem: Profile names are case-sensitive. Using @Profile("Dev")
but activating with -Dspring.profiles.active=dev
won't work.
Solution: Standardize on lowercase profile names and be consistent.
Too Many Profiles
Problem: Having too many specialized profiles can make the application configuration hard to understand.
Solution: Use profile groups and combine profiles logically.
Missing Default Behavior
Problem: Without a default profile, your application might behave unpredictably when no profiles are active.
Solution: Always provide a default configuration either with @Profile("default")
or without any profile annotation.
Summary
Spring Profiles provide a powerful mechanism to adapt your application's behavior to different environments and scenarios. By allowing conditional bean registration and environment-specific properties, profiles make your Spring applications more flexible and maintainable.
Key takeaways:
- Use
@Profile
annotation to specify when beans or configurations should be active - Activate profiles using properties, environment variables, or programmatically
- Leverage profile-specific property files for environment-specific configurations
- Use logical expressions for more complex profile conditions
- Follow best practices to avoid common pitfalls
Additional Resources
- Spring Framework Documentation on Profiles
- Spring Boot Profile Configuration
- Baeldung: Guide to Spring Profiles
Exercises
-
Create a Spring Boot application with three profiles: "dev", "test", and "prod". Configure each profile to use a different database (H2, MySQL, PostgreSQL).
-
Implement a feature flag system using Spring Profiles to enable/disable specific features in your application.
-
Set up a Spring application that uses profile expressions to conditionally include beans based on complex conditions (e.g., "production & !europe" or "development | testing").
-
Create a profile group that combines multiple existing profiles and demonstrate its usage in a Spring Boot application.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)