Environment variables are essential in modern app development because they let you keep configuration separate from your code. If you're using Spring Boot—a popular Java framework—you’ll appreciate how easily it handles environment variables for externalized configurations.
This makes your applications more portable, scalable, and secure. In this guide, we’ll walk through how you can set up and use environment variables in Spring Boot, along with practical solutions and best practices.
🚀 Join Index.dev and get matched with top global companies for high-paying remote Java roles!
Core Concepts
Spring Boot implements a hierarchical property resolution system that determines how different configuration sources override each other. Understanding this hierarchy enables you to manage configurations effectively, ensuring consistency and adaptability across diverse environments.
- Command-line arguments
- JNDI attributes from java:comp/env
- JVM system properties
- OS environment variables
- Profile-specific properties
- Application properties/YAML files
Together, let us explore some methods for configuration management, while also addressing specific challenges and solutions that you might face in real-world scenarios.
Read More: How to Identify and Optimize Long-Running Queries in Java
Method 1: Type-safe Configuration Properties
Introduction
Enterprise applications need reliable, validated configuration management that catches errors at startup rather than runtime.
Problem Tackled
- Loose typing with @Value annotations leads to runtime errors
- No validation at startup
- Mutable state in configuration objects
- Difficult testing and maintenance
Solution
Implement strongly-typed configuration classes using @ConfigurationProperties with validation and immutability, following the Spring Boot best practices for externalized configuration.
Code Example
@Validated
@Configuration
@ConfigurationProperties(prefix = "app")
public class ApplicationConfig {
private final DatabaseConfig database;
private final SecurityConfig security;
private final Map<String, String> features;
@ConstructorBinding
public ApplicationConfig(
@NotNull DatabaseConfig database,
Optional<SecurityConfig> security,
Optional<Map<String, String>> features) {
this.database = database;
this.security = security.orElse(new SecurityConfig());
this.features = features.orElse(Map.of());
}
@Validated
public static class DatabaseConfig {
@NotNull
@Pattern(regexp = "^jdbc:.*", message = "Must be a valid JDBC URL")
private final String url;
private final String username;
private final Duration timeout;
private final int poolSize;
@ConstructorBinding
public DatabaseConfig(
String url,
String username,
Optional<Duration> timeout,
Optional<Integer> poolSize) {
this.url = url;
this.username = username;
this.timeout = timeout.orElse(Duration.ofSeconds(30));
this.poolSize = poolSize.orElse(10);
}
}
}Usage Example
app:
database:
url: jdbc:postgresql://localhost:5432/mydb
username: ${DB_USER}
timeout: PT30S
pool-size: 20
security:
key-store: prod.jks
session-timeout: PT1H
features:
new-auth-flow: true
…
java
@Service
@RequiredArgsConstructor
public class ExampleService {
private final ApplicationConfig config;
public void performOperation() {
Duration timeout = config.getDatabase().getTimeout();
// Use configuration...
}
}Explanation
Type-safe configuration properties solve several critical challenges in enterprise configuration management. When you implement them with Spring Boot's @ConfigurationProperties and Jakarta Bean Validation (JSR-380), you get comprehensive validation and type safety.
The design uses constructor binding (@ConstructorBinding) to ensure immutability, preventing runtime modifications that could lead to inconsistent application states. By organizing configurations with nested static classes, we can create logical hierarchies that make your structure more maintainable and self-documenting.
Key technical points:
- @ConstructorBinding ensures configuration immutability by requiring all values to be set at construction time
- Optional<T> parameters enable flexible configuration with sensible defaults, reducing boilerplate code
- JSR-380 validation annotations (@NotNull, @Pattern, etc.) provide immediate feedback on invalid configurations
- Nested static classes create logical configuration groupings that mirror the YAML structure
- Type-safe properties eliminate runtime type conversion errors common with @Value annotations
- Configuration validation happens at application startup, failing fast if required properties are missing or invalid
- The approach enables IDE auto-completion and type checking, improving developer productivity
Method 2: Profile-Based Configuration
Introduction
Different environments (development, staging, production) require different configurations while maintaining the same codebase.
Problem Tackled
- Managing environment-specific settings
- Switching configurations during development
- Maintaining security across environments
Solution
Leverage Spring profiles with YAML configuration files.
Code Example
# application.yml
spring:
profiles:
group:
development: dev-db, dev-cache
production: prod-db, prod-cache
---
spring.config.activate.on-profile: dev-db
app:
database:
url: jdbc:h2:mem:devdb
pool-size: 5
---
spring.config.activate.on-profile: prod-db
app:
database:
url: jdbc:postgresql://prod-host:5432/proddb
pool-size: 50
Alternative Properties Approach
properties
# application-dev.properties
spring.datasource.url=jdbc:mysql://localhost:3306/devdb
spring.datasource.username=devuser
Usage Example
# Activate via command line
java -jar myapp.jar --spring.profiles.active=development
# Activate via environment variable
export SPRING_PROFILES_ACTIVE=development
java -jar myapp.jarExplanation
Profile-based configuration solves several critical challenges in enterprise applications. The implementation leverages Spring Boot's profile activation mechanism to load environment-specific configurations automatically. With YAML-based profiles, you can group related configurations (like dev-db with dev-cache) logically. If simplicity is what you prefer, properties files offer an alternative for basic scenarios.
Key technical points:
- Profile groups reduce activation complexity by bundling related profiles
- The spring.config.activate.on-profile selector ensures precise profile matching
- Properties files (application-{profile}.properties) provide a straightforward way to organize configurations
- Spring's profile resolution ensures the correct configuration is loaded based on the active profile
- Property values can include references to environment variables for additional flexibility
Advanced Configuration Patterns
Secure Configuration Management
Enterprise applications often need to manage sensitive configuration data securely. As recommended in the Spring Cloud Vault Reference, sensitive configuration should be managed using secure vaults which provides robust integration with HashiCorp Vault for secure credential management.
@Configuration
@EnableVaultConfig
public class VaultConfig {
@Value("${vault.uri}")
private String vaultUri;
@Bean
public VaultTemplate vaultTemplate() {
VaultEndpoint endpoint = VaultEndpoint.from(vaultUri);
return new VaultTemplate(endpoint,
new TokenAuthentication(System.getenv("VAULT_TOKEN")));
}
}Dynamic Configuration Updates
For configurations that need to change without application restart, Spring Cloud Config with @RefreshScope enables dynamic updates:
@RefreshScope
@Service
@RequiredArgsConstructor
public class DynamicConfigService {
private final VaultTemplate vaultTemplate;
@Value("${app.feature.enabled:false}")
private boolean featureEnabled;
public void performOperation() {
if (featureEnabled) {
// Feature logic
}
}
}Explanation
These advanced patterns address crucial enterprise requirements for security and flexibility. The Vault integration provides a secure way to manage sensitive configurations, while @RefreshScope enables runtime configuration updates without service interruption.
Key technical points:
- Vault integration separates sensitive data from application code
- @RefreshScope ensures beans can be recreated with updated configurations
- Dynamic updates support feature flags and configuration changes in production
- Secure credential rotation becomes possible without application restart
- Centralized configuration management improves security and maintainability
Method 3: Command-Line and System Property Configuration
Introduction
Runtime configuration management often requires flexibility to override settings without rebuilding or redeploying applications. This is where command-line and system property configuration comes into play. Spring Boot’s support for command-line arguments provides a powerful way to dynamically override configuration properties during runtime, enabling quick adjustments across different environments. For more details, you can refer to the official Spring Boot documentation on command-line arguments.
Problem Tackled
- Need for dynamic configuration changes in production
- Environment-specific overrides without code changes
- Quick testing and debugging scenarios
- Emergency configuration updates
- Different configurations for different deployment scenarios
Solution
Utilize command-line arguments and system properties to provide configuration overrides that take precedence over application properties and YAML files.
Code Example
@Configuration
@Slf4j
public class CommandLineConfig {
@Bean
public CommandLinePropertySource<?> commandLinePropertySource(
ApplicationArguments args) {
return new SimpleCommandLinePropertySource(args.getSourceArgs());
}
@PostConstruct
public void logActiveProperties() {
log.info("Active command line properties: {}",
System.getProperties().stringPropertyNames().stream()
.filter(key -> key.startsWith("app."))
.collect(Collectors.toMap(
key -> key,
System::getProperty
)));
}
}Usage Example
# System Properties
java -Dapp.database.url=jdbc:postgresql://localhost:5432/testdb \
-Dapp.database.pool-size=25 \
-jar app.jar
# Command-line arguments
java -jar app.jar \
--app.database.url=jdbc:postgresql://localhost:5432/testdb \
--app.database.pool-size=25
# Environment variables
export APP_DATABASE_URL=jdbc:postgresql://localhost:5432/testdb
export APP_DATABASE_POOL_SIZE=25
java -jar app.jarExplanation
Command-line and system property configuration provides a flexible way to override application settings at runtime, as specified in the Spring Boot External Configuration documentation. This approach is particularly valuable for DevOps scenarios where different environments require different configurations.
Key technical points:
- Command-line arguments take highest precedence in Spring Boot's property hierarchy
- System properties provide a robust way to set JVM-wide configuration
- Properties can be changed without modifying application code or configuration files
- Supports both relaxed binding (--app.database.url or APP_DATABASE_URL)
- Enables quick configuration changes for testing and debugging
- Provides audit trail through application logs
Method 4: External Configuration Sources
Introduction
Enterprise applications often require centralized configuration management and secure credential handling. Spring Cloud Config and Vault integration provide robust solutions for these requirements.
Problem Tackled
- Centralized configuration management
- Secure handling of sensitive data
- Dynamic configuration updates
- Configuration version control and audit
- Environment-specific secret management
Solution
Integrate with external configuration services and secure vaults using Spring Cloud Config and HashiCorp Vault, following the Spring Cloud Config Server architecture.
Code Example
@Configuration
@EnableConfigurationProperties
@EnableConfigServer
public class ExternalConfigConfiguration {
@Bean
@ConfigurationProperties(prefix = "vault.config")
public VaultProperties vaultProperties() {
return new VaultProperties();
}
@Bean
public VaultTemplate vaultTemplate(VaultProperties properties) {
VaultEndpoint endpoint = VaultEndpoint.from(properties.getUrl());
ClientAuthentication auth = new TokenAuthentication(
properties.getToken());
return new VaultTemplate(endpoint, auth);
}
@Bean
@RefreshScope
public ConfigurationPropertiesHolder configProps() {
return new ConfigurationPropertiesHolder();
}
}
@RefreshScope
@Component
@Slf4j
public class ConfigurationPropertiesHolder {
@Value("${app.feature.enabled:false}")
private boolean featureEnabled;
@EventListener(RefreshScopeRefreshedEvent.class)
public void onRefresh(RefreshScopeRefreshedEvent event) {
log.info("Configuration refreshed, feature enabled: {}",
featureEnabled);
}
}Usage Example
# bootstrap.yml
spring:
cloud:
config:
uri: http://config-server:8888
fail-fast: true
vault:
host: vault.example.com
port: 8200
scheme: https
# Trigger configuration refresh
curl -X POST http://localhost:8080/actuator/refreshExplanation
External configuration sources provide a comprehensive solution for managing configurations across multiple services and environments, as detailed in the Spring Cloud documentation. This approach enables centralized configuration management while maintaining security and providing dynamic update capabilities.
Key technical points:
- Spring Cloud Config Server provides version-controlled configuration management
- Vault integration ensures secure credential handling and rotation
- @RefreshScope enables dynamic configuration updates without application restarts
- Event listeners allow for automated actions on configuration changes
- Fail-fast capability ensures applications don't start with invalid configurations
- Centralized management reduces configuration drift and improves security
- Integration with Spring Boot Actuator enables configuration monitoring and refresh endpoints
Best Practices and Recommendations
Security
- Never commit sensitive data to version control
- Use secure vaults for credentials
- Encrypt sensitive configuration values
Organization
- Group related configurations
- Use meaningful property names
- Document configuration options
Validation
- Implement validation for all critical configurations
- Fail fast on invalid configurations
- Provide clear error messages
Monitoring
- Log configuration changes
- Track configuration access patterns
- Monitor for security violations
Explore More: How to Deserialize JSON in Java Using a Mapper
Conclusion
Managing environment configurations effectively is key to building secure, scalable, and maintainable applications. With Spring Boot’s powerful configuration features, you can set up type-safe, validated, and environment-specific settings that grow with your application.
Whether you're working with microservices or a monolithic setup, this guide will help you apply best practices to keep your Java applications professional and resilient.
🚀 Looking for high-paying remote Java roles? Join Index.dev and get matched with top global companies. Work on innovative projects, grow your career, and enjoy the flexibility of remote work. Sign up today!