Spring Environment Configuration
In modern application development, your code will run in multiple environments - development, testing, staging, and production. Each environment has different requirements: development might use an in-memory database, while production connects to a high-availability cluster. Spring's environment configuration capabilities let you manage these differences elegantly.
Introduction to Spring Environment
Spring's Environment
abstraction provides a unified interface for property management and profile configuration. It helps you:
- Define different behaviors for different environments
- Externalize configuration from your code
- Override configuration based on deployment context
- Maintain environment-specific properties
Let's explore how to effectively manage environment configurations in Spring applications.
Spring Profiles
Profiles are a core mechanism for conditional component registration and configuration property selection in Spring.
What Are Profiles?
Profiles let you define beans that should only be created in specific environments. For example, you might have:
- A development profile that uses an embedded database
- A production profile that connects to a real database server
- A test profile that uses mock services
Configuring Profiles
Using Annotations
You can mark beans to be included only when specific profiles are active:
@Component
@Profile("development")
public class DevDatabaseConfig implements DatabaseConfig {
// Development database configuration
}
@Component
@Profile("production")
public class ProdDatabaseConfig implements DatabaseConfig {
// Production database configuration
}
You can also combine multiple profiles using logical operators:
@Profile("production & !eu") // Active in production but not EU region
@Profile("development | test") // Active in either development or test
Using XML Configuration
For XML-based configurations:
<beans profile="development">
<!-- beans that only activate in development profile -->
</beans>
Activating Profiles
You can activate profiles in several ways:
Using application.properties/application.yml
# application.properties
spring.profiles.active=development
Or in YAML format:
# application.yml
spring:
profiles:
active: development
Using JVM System Properties
When starting your application:
java -jar myapp.jar -Dspring.profiles.active=production
Using Environment Variables
export SPRING_PROFILES_ACTIVE=production
java -jar myapp.jar
Programmatically
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.setAdditionalProfiles("production");
application.run(args);
}
}
Profile Groups
Spring Boot 2.4+ lets you define profile groups to activate multiple profiles together:
spring.profiles.group.production=prod-db,prod-logging,metrics
Now, activating the production
profile automatically activates prod-db
, prod-logging
, and metrics
profiles.
Property Sources
Spring's environment abstraction uses a hierarchy of property sources to resolve configuration values.
Property Resolution Order
Spring checks these sources (from highest to lowest priority):
- Command line arguments
- JNDI attributes from
java:comp/env
- JVM system properties (
System.getProperties()
) - OS environment variables
- Profile-specific application properties
- Application properties
@PropertySource
annotations- Default properties
Common Property Sources
application.properties/application.yml
The standard configuration files located in:
/config
subdirectory of the working directory- Working directory
config
package in the classpath- Root of classpath
# application.properties
server.port=8080
app.feature.enabled=true
app.cache.timeout=3600
The YAML equivalent:
# application.yml
server:
port: 8080
app:
feature:
enabled: true
cache:
timeout: 3600
Profile-Specific Properties
Spring loads properties from profile-specific files when a profile is active:
application-development.properties
application-production.properties
application-test.properties
External Configuration
For production deployment, externalize configuration:
java -jar myapp.jar --spring.config.location=file:/path/to/config/
@PropertySource Annotation
Load additional property files:
@Configuration
@PropertySource("classpath:db/database.properties")
public class AppConfig {
// Configuration
}
Accessing Environment Properties
Spring provides several ways to access configuration properties in your code.
Using the Environment Object
@Autowired
private Environment environment;
public void someMethod() {
String serverName = environment.getProperty("app.server.name");
int serverPort = environment.getProperty("app.server.port", Integer.class, 8080);
boolean isEnabled = environment.getProperty("app.feature.enabled", Boolean.class, false);
// Check if a property exists
if (environment.containsProperty("app.feature.new")) {
// Use the new feature
}
// Check if a profile is active
if (environment.acceptsProfiles(Profiles.of("production"))) {
// Do production-specific initialization
}
}
Using @Value Annotation
Inject properties directly into fields:
@Component
public class MyService {
@Value("${app.server.name}")
private String serverName;
@Value("${app.server.port:8080}")
private int serverPort; // Default is 8080
@Value("${app.feature.enabled:false}")
private boolean featureEnabled;
}
Using @ConfigurationProperties
Group related properties into typed Java objects:
@Component
@ConfigurationProperties(prefix = "app.server")
public class ServerProperties {
private String name;
private int port = 8080; // Default value
private boolean secure = false;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public boolean isSecure() { return secure; }
public void setSecure(boolean secure) { this.secure = secure; }
}
Then in your properties file:
app.server.name=production-server
app.server.port=443
app.server.secure=true
And use it in your code:
@Service
public class NetworkService {
private final ServerProperties serverProperties;
@Autowired
public NetworkService(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
public void connect() {
String protocol = serverProperties.isSecure() ? "https" : "http";
String serverUrl = protocol + "://" + serverProperties.getName() + ":" + serverProperties.getPort();
// Connect to the server
}
}
Practical Example: Multi-Environment Application
Let's build a simple Spring Boot application with environment-specific configurations.
Project Structure
src/
└── main/
├── java/
│ └── com/
│ └── example/
│ ├── DemoApplication.java
│ ├── config/
│ │ ├── DevDatabaseConfig.java
│ │ └── ProdDatabaseConfig.java
│ └── service/
│ └── NotificationService.java
└── resources/
├── application.properties
├── application-development.properties
└── application-production.properties
Configuration Classes
// DemoApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
// DevDatabaseConfig.java
package com.example.config;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@Configuration
@Profile("development")
public class DevDatabaseConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("schema.sql")
.addScript("dev-data.sql")
.build();
}
}
// ProdDatabaseConfig.java
package com.example.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
@Configuration
@Profile("production")
public class ProdDatabaseConfig {
@Value("${db.url}")
private String dbUrl;
@Value("${db.username}")
private String dbUsername;
@Value("${db.password}")
private String dbPassword;
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(dbUrl);
dataSource.setUsername(dbUsername);
dataSource.setPassword(dbPassword);
return dataSource;
}
}
// NotificationService.java
package com.example.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
@Value("${notification.service.url}")
private String serviceUrl;
@Value("${notification.service.token:}")
private String serviceToken;
@Value("${notification.enabled}")
private boolean notificationsEnabled;
public void sendNotification(String message) {
if (!notificationsEnabled) {
System.out.println("Notifications disabled: " + message);
return;
}
System.out.println("Sending to " + serviceUrl);
if (!serviceToken.isEmpty()) {
System.out.println("Using auth token: " + serviceToken);
}
System.out.println("Message: " + message);
}
}
Property Files
# application.properties (common settings)
spring.application.name=demo-app
server.port=8080
notification.enabled=true
# application-development.properties
notification.service.url=http://localhost:8888/notify
notification.enabled=false
logging.level.com.example=DEBUG
# application-production.properties
notification.service.url=https://notification-service.example.com/api/notify
notification.service.token=${NOTIFICATION_TOKEN}
db.url=jdbc:mysql://prod-db.example.com:3306/myapp
db.username=${DB_USERNAME}
db.password=${DB_PASSWORD}
logging.level.com.example=INFO
Running the Application
For development:
java -jar demo-app.jar --spring.profiles.active=development
Output:
... Starting DemoApplication with development profile
... Initialized embedded H2 database
... Notifications disabled: Application started
For production:
# Set required environment variables
export NOTIFICATION_TOKEN=abc123
export DB_USERNAME=produser
export DB_PASSWORD=s3cr3t
java -jar demo-app.jar --spring.profiles.active=production
Output:
... Starting DemoApplication with production profile
... Connected to MySQL database at prod-db.example.com
... Sending to https://notification-service.example.com/api/notify
... Using auth token: abc123
... Message: Application started
Best Practices for Environment Configuration
-
Separate code from configuration: Never hardcode environment-specific values in your code.
-
Use meaningful profiles: Name them for specific purposes rather than environments (e.g.,
inmemory-db
rather than justdev
). -
Keep secrets secure: Use vault systems, environment variables or encrypted property files for sensitive data.
-
Provide sensible defaults: Use default values for properties when appropriate:
java@Value("${cache.timeout:60}") // Default to 60 seconds
-
Document required properties: Comment your property files and document required environment variables.
-
Use property validation: Add validation to detect missing or invalid properties:
java@ConfigurationProperties(prefix = "app.mail")
@Validated
public class MailProperties {
@NotNull
private String host;
@Min(1)
@Max(65535)
private int port = 25;
// Getters and setters
} -
Use relaxed binding: Spring allows various formats for the same property:
properties# All these map to the same property
app.page-size=100
app.pageSize=100
APP_PAGE_SIZE=100 -
Test with different profiles: Include tests that verify your application behaves correctly with different profiles.
Summary
Spring's environment configuration provides powerful tools for managing your application across different environments:
- Profiles let you activate different beans and configurations based on the environment
- Property sources establish a hierarchy for resolving configuration values
- Configuration properties provide type-safe access to externalized configuration
- Environment abstraction unifies access to profiles and properties
Properly configuring your Spring application for different environments improves maintainability, security, and flexibility. By separating configuration from code and using Spring's environment tools, you can easily deploy the same application across development, testing, and production environments.
Additional Resources
Exercises
-
Create a Spring Boot application that serves different content based on profiles (e.g.,
dev
,staging
,prod
). -
Implement a
@ConfigurationProperties
class to hold database connection settings, and use different property files for different environments. -
Write a test that verifies your application behaves correctly when switching between profiles.
-
Extend the example application to load configuration from a remote source (e.g., Spring Cloud Config).
-
Create a profile-based bean that provides sample data in development but real data in production.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)