Skip to main content

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:

java
@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:

java
@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:

java
// 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

properties
# application.properties
spring.profiles.active=development,local

Or in YAML format:

yaml
# application.yml
spring:
profiles:
active: development,local

Using Environment Variables

bash
export SPRING_PROFILES_ACTIVE=production

Using JVM System Properties

bash
java -jar -Dspring.profiles.active=production myapp.jar

Programmatically

java
@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 or application-{profile}.yml

For example:

  • application.properties: Common properties for all profiles
  • application-development.properties: Development-specific properties
  • application-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:

properties
# 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:

java
@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

java
public interface MessageService {
String getMessage();
String getEnvironmentName();
}

Environment-specific Implementations

java
@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

java
@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

java
@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

properties
# 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:

bash
java -jar -Dspring.profiles.active=dev demo.jar

Output when accessing /environment:

Current environment: Development

To run with the "prod" profile:

bash
java -jar -Dspring.profiles.active=prod demo.jar

Output when accessing /environment:

Current environment: Production

Best Practices for Spring Profiles

  1. Use meaningful profile names: Choose descriptive names like "development", "testing", "production" rather than abbreviations.

  2. Keep a default configuration: Provide sensible defaults for when no specific profile is activated.

  3. Don't hardcode profile names: Consider using constants for profile names to avoid typos and make refactoring easier.

  4. Define a clear profile strategy: Document which profiles are available and when they should be used.

  5. Be cautious with profile-specific beans: When two active profiles both define the same bean, you might encounter conflicts.

  6. Use profile groups (Spring Boot 2.4+): You can define profile groups to activate multiple profiles at once:

yaml
spring:
profiles:
group:
development: dev,local,debug
production: prod,cloud,no-debug
  1. 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

Exercises

  1. Create a Spring Boot application with three profiles: "dev", "test", and "prod". Configure each profile to use a different database (H2, MySQL, PostgreSQL).

  2. Implement a feature flag system using Spring Profiles to enable/disable specific features in your application.

  3. Set up a Spring application that uses profile expressions to conditionally include beans based on complex conditions (e.g., "production & !europe" or "development | testing").

  4. 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! :)