google-site-verification: google61fe8ba583a51912.html
Profile-Specific Application Properties in Java

Overview

Profile-specific properties allow you to configure your application differently for various environments (development, testing, production, etc.) without code changes. Spring Boot provides robust support for environment-specific configuration.

Core Concepts

  • Profiles: Named groups of configuration (dev, test, prod, etc.)
  • Property Sources: Hierarchical property loading
  • Profile Activation: Ways to enable specific profiles
  • Property Overrides: Order of precedence for properties

Basic Configuration

1. Profile-Specific Property Files

src/main/resources/ ├── application.properties # Default properties ├── application-dev.properties # Development profile ├── application-test.properties # Test profile ├── application-prod.properties # Production profile └── application-local.properties # Local development

2. Property File Examples

application.properties (default):

# Default configuration app.name=MyApplication app.version=1.0.0 server.port=8080 spring.datasource.url=jdbc:h2:mem:defaultdb spring.jpa.hibernate.ddl-auto=create-drop logging.level.root=INFO # Profile activation (can be overridden externally) spring.profiles.active=dev

application-dev.properties:

# Development specific configuration server.port=8080 spring.datasource.url=jdbc:h2:mem:devdb spring.datasource.username=devuser spring.datasource.password=devpass spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true logging.level.com.myapp=DEBUG logging.level.org.hibernate.SQL=DEBUG # Feature flags app.features.cache-enabled=false app.features.email-enabled=false app.features.analytics-enabled=false # External services (development endpoints) app.services.payment.url=http://localhost:8081/payment app.services.notification.url=http://localhost:8082/notification

application-test.properties:

# Test environment configuration server.port=0 # Random port for tests spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 spring.datasource.username=testuser spring.datasource.password=testpass spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=false # Test-specific settings spring.main.lazy-initialization=true spring.jpa.defer-datasource-initialization=true # Feature flags for testing app.features.cache-enabled=true app.features.email-enabled=false # Don't send emails in tests app.features.analytics-enabled=false # Test endpoints app.services.payment.url=http://test-payment:8081 app.services.notification.url=http://test-notification:8082

application-prod.properties:

# Production configuration server.port=8080 server.compression.enabled=true server.servlet.session.timeout=30m # Production database spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5432/proddb} spring.datasource.username=${DB_USERNAME:produser} spring.datasource.password=${DB_PASSWORD:prodpass} spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=false spring.jpa.properties.hibernate.format_sql=false # Production logging logging.level.root=WARN logging.level.com.myapp=INFO logging.file.name=/var/log/myapp/application.log logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n # Production features app.features.cache-enabled=true app.features.email-enabled=true app.features.analytics-enabled=true # Production services app.services.payment.url=https://payment.myapp.com app.services.notification.url=https://notification.myapp.com # Security app.security.require-ssl=true app.security.cors.allowed-origins=https://myapp.com # Monitoring management.endpoints.web.exposure.include=health,metrics,info management.endpoint.health.show-details=when_authorized

YAML Configuration

1. YAML Profile Configuration

application.yml:

# Default configuration app: name: MyApplication version: 1.0.0 security: jwt-secret: default-secret-key token-expiration: 86400000 server: port: 8080 spring: datasource: url: jdbc:h2:mem:defaultdb driver-class-name: org.h2.Driver jpa: hibernate: ddl-auto: create-drop show-sql: false profiles: active: dev logging: level: root: INFO com.myapp: DEBUG --- # Development profile spring: config: activate: on-profile: dev server: port: 8080 spring: datasource: url: jdbc:h2:mem:devdb username: devuser password: devpass jpa: show-sql: true hibernate: ddl-auto: update properties: hibernate: format_sql: true logging: level: com.myapp: DEBUG org.hibernate.SQL: DEBUG org.hibernate.type: TRACE app: features: cache-enabled: false email-enabled: false analytics-enabled: false services: payment: url: http://localhost:8081/payment timeout: 5000 notification: url: http://localhost:8082/notification timeout: 3000 --- # Test profile spring: config: activate: on-profile: test server: port: 0 # Random port spring: datasource: url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 username: testuser password: testpass jpa: hibernate: ddl-auto: create-drop show-sql: false main: lazy-initialization: true app: features: cache-enabled: true email-enabled: false analytics-enabled: false services: payment: url: http://test-payment:8081 timeout: 2000 notification: url: http://test-notification:8082 timeout: 2000 --- # Production profile spring: config: activate: on-profile: prod server: port: 8080 compression: enabled: true servlet: session: timeout: 30m spring: datasource: url: ${DB_URL:jdbc:postgresql://localhost:5432/proddb} username: ${DB_USERNAME:produser} password: ${DB_PASSWORD:prodpass} hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 jpa: hibernate: ddl-auto: validate show-sql: false properties: hibernate: jdbc: batch_size: 20 order_inserts: true order_updates: true logging: level: root: WARN com.myapp: INFO file: name: /var/log/myapp/application.log pattern: console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n" app: features: cache-enabled: true email-enabled: true analytics-enabled: true services: payment: url: https://payment.myapp.com timeout: 10000 notification: url: https://notification.myapp.com timeout: 5000 security: cors: allowed-origins: "https://myapp.com" require-ssl: true management: endpoints: web: exposure: include: health,metrics,info endpoint: health: show-details: when_authorized show-components: when_authorized

Profile Activation Methods

1. Programmatic Profile Activation

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.core.env.Environment; @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication app = new SpringApplication(MyApplication.class); // Method 1: Set active profiles programmatically app.setAdditionalProfiles("dev", "local"); // Method 2: Use default profiles app.setDefaultProperties(Collections.singletonMap( "spring.profiles.default", "dev" )); app.run(args); } }

2. Configuration Class for Profiles

import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.PropertySource; @Configuration public class ProfileConfig { @Configuration @Profile("dev") @PropertySource("classpath:application-dev.properties") static class DevConfig { // Development-specific beans } @Configuration @Profile("test") @PropertySource({"classpath:application-test.properties", "classpath:test-overrides.properties"}) static class TestConfig { // Test-specific beans } @Configuration @Profile("prod") @PropertySource("classpath:application-prod.properties") static class ProdConfig { // Production-specific beans } }

Profile-Specific Beans

1. Conditional Bean Configuration

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @Configuration public class ServiceConfiguration { // Development: In-memory database @Bean @Profile({"dev", "test"}) public DataSource devDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .setName("devdb") .build(); } // Production: External database @Bean @Profile("prod") public DataSource prodDataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(env.getProperty("spring.datasource.url")); dataSource.setUsername(env.getProperty("spring.datasource.username")); dataSource.setPassword(env.getProperty("spring.datasource.password")); return dataSource; } // Development: Mock email service @Bean @Profile({"dev", "test"}) public EmailService mockEmailService() { return new MockEmailService(); } // Production: Real email service @Bean @Profile("prod") public EmailService prodEmailService() { return new SMTPEmailService( env.getProperty("app.email.host"), Integer.parseInt(env.getProperty("app.email.port")) ); } // Feature flags based on profiles @Bean @Profile("!test") public AnalyticsService analyticsService() { return new GoogleAnalyticsService(); } @Bean @Profile("test") public AnalyticsService mockAnalyticsService() { return new MockAnalyticsService(); } }

2. Conditional Configuration with @Conditional

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @Configuration public class ConditionalConfiguration { @Bean @ConditionalOnProperty(name = "app.features.cache-enabled", havingValue = "true") public CacheManager cacheManager() { return new RedisCacheManager(redisTemplate()); } @Bean @ConditionalOnExpression("'${spring.profiles.active}'.contains('dev')") public DevelopmentTools developmentTools() { return new DevelopmentTools(); } @Bean @ConditionalOnProperty(prefix = "app.security", name = "require-ssl", havingValue = "true") public SecurityConfig sslSecurityConfig() { return new SSLSecurityConfig(); } }

Advanced Profile Strategies

1. Composite Profiles

# application-cloud.properties spring.cloud.aws.region=us-east-1 app.deployment.env=cloud app.services.discovery.enabled=true # application-aws.properties app.cloud.provider=aws app.services.s3.enabled=true # application-azure.properties app.cloud.provider=azure app.services.blob-storage.enabled=true # application-docker.properties app.deployment.containerized=true spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:5432/${DB_NAME:mydb}

2. Profile Groups (Spring Boot 2.4+)

# In application.properties spring.profiles.group.dev=dev,local,debug spring.profiles.group.test=test,integration spring.profiles.group.prod=prod,cloud,monitoring spring.profiles.group.cloud=cloud,aws,kubernetes
# In application.yml spring: profiles: group: dev: "dev,local,debug" test: "test,integration" prod: "prod,cloud,monitoring" cloud: "cloud,aws,kubernetes"

Property Injection and Usage

1. Configuration Properties Classes

import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.Map; @Component @ConfigurationProperties(prefix = "app") @Validated public class ApplicationProperties { @NotBlank private String name; private String version; private Features features; private Map<String, Service> services; private Security security; // Static nested classes for structured properties public static class Features { private boolean cacheEnabled; private boolean emailEnabled; private boolean analyticsEnabled; // Getters and setters public boolean isCacheEnabled() { return cacheEnabled; } public void setCacheEnabled(boolean cacheEnabled) { this.cacheEnabled = cacheEnabled; } // ... other getters/setters } public static class Service { private String url; private int timeout = 5000; private int retryAttempts = 3; // Getters and setters public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } // ... other getters/setters } public static class Security { private boolean requireSsl = false; private Cors cors = new Cors(); public static class Cors { private String[] allowedOrigins = {"*"}; private String[] allowedMethods = {"GET", "POST", "PUT", "DELETE"}; // Getters and setters public String[] getAllowedOrigins() { return allowedOrigins; } public void setAllowedOrigins(String[] allowedOrigins) { this.allowedOrigins = allowedOrigins; } // ... other getters/setters } // Getters and setters public boolean isRequireSsl() { return requireSsl; } public void setRequireSsl(boolean requireSsl) { this.requireSsl = requireSsl; } // ... other getters/setters } // Getters and setters for top-level properties public String getName() { return name; } public void setName(String name) { this.name = name; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public Features getFeatures() { return features; } public void setFeatures(Features features) { this.features = features; } public Map<String, Service> getServices() { return services; } public void setServices(Map<String, Service> services) { this.services = services; } public Security getSecurity() { return security; } public void setSecurity(Security security) { this.security = security; } }

2. Using @Value Annotation

import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class ServiceClient { @Value("${app.services.payment.url}") private String paymentServiceUrl; @Value("${app.services.payment.timeout:5000}") private int paymentServiceTimeout; @Value("${app.features.cache-enabled:false}") private boolean cacheEnabled; @Value("${spring.profiles.active:default}") private String activeProfiles; public void processPayment() { if (activeProfiles.contains("dev")) { System.out.println("Development mode - using: " + paymentServiceUrl); } // Implementation } }

3. Environment-Aware Services

import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @Service public class EnvironmentAwareService { private final Environment environment; private final ApplicationProperties appProperties; public EnvironmentAwareService(Environment environment, ApplicationProperties appProperties) { this.environment = environment; this.appProperties = appProperties; } public boolean isDevelopment() { return environment.acceptsProfiles(Profiles.of("dev", "local")); } public boolean isProduction() { return environment.acceptsProfiles(Profiles.of("prod")); } public boolean isFeatureEnabled(String feature) { switch (feature) { case "cache": return appProperties.getFeatures().isCacheEnabled(); case "email": return appProperties.getFeatures().isEmailEnabled(); case "analytics": return appProperties.getFeatures().isAnalyticsEnabled(); default: return false; } } public String getServiceUrl(String serviceName) { ApplicationProperties.Service service = appProperties.getServices().get(serviceName); return service != null ? service.getUrl() : null; } public void logEnvironmentInfo() { String[] activeProfiles = environment.getActiveProfiles(); if (activeProfiles.length == 0) { System.out.println("No active profiles - using default configuration"); } else { System.out.println("Active profiles: " + String.join(", ", activeProfiles)); } } }

Testing with Profiles

1. Test Configuration

import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; // Method 1: Using @ActiveProfiles @SpringBootTest @ActiveProfiles({"test", "integration"}) class IntegrationTest { @Test void testWithTestProfile() { // Test implementation } } // Method 2: Using @TestPropertySource @SpringBootTest @TestPropertySource(properties = { "app.features.cache-enabled=false", "app.services.payment.url=http://localhost:8081/mock-payment", "spring.datasource.url=jdbc:h2:mem:testdb" }) class PropertySourceTest { @Test void testWithCustomProperties() { // Test implementation } } // Method 3: Profile-specific test properties @SpringBootTest @ActiveProfiles("test") class ProfileSpecificTest { @Test void testWithProfileProperties() { // Uses application-test.properties } }

2. Test Configuration Classes

import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; @TestConfiguration @Profile("test") public class TestConfig { @Bean @Primary public EmailService testEmailService() { return new MockEmailService(); } @Bean @Primary public PaymentService testPaymentService() { return new MockPaymentService(); } @Bean public TestDataLoader testDataLoader() { return new TestDataLoader(); } } // Usage in tests @SpringBootTest @ActiveProfiles("test") @Import(TestConfig.class) class ServiceTest { @Autowired private EmailService emailService; // Injects MockEmailService @Test void testService() { // Test with mocked dependencies } }

Deployment and External Configuration

1. External Property Files

# Command line to run with external config java -jar myapp.jar \ --spring.config.location=file:/etc/myapp/application.properties \ --spring.profiles.active=prod # Or using environment variables export SPRING_PROFILES_ACTIVE=prod export SPRING_CONFIG_LOCATION=file:/etc/myapp/ java -jar myapp.jar

2. Docker and Kubernetes Configuration

Dockerfile:

FROM openjdk:17-jre-slim # Copy application COPY target/myapp.jar /app/myapp.jar # Create config directory RUN mkdir -p /etc/myapp/ # External configuration volume VOLUME /etc/myapp # Run with external config CMD ["java", "-jar", "/app/myapp.jar", \ "--spring.config.location=file:/etc/myapp/application.properties"]

Kubernetes ConfigMap:

apiVersion: v1 kind: ConfigMap metadata: name: myapp-config data: application-prod.properties: | app.name=MyApplication server.port=8080 spring.datasource.url=jdbc:postgresql://db-host:5432/proddb app.features.cache-enabled=true app.services.payment.url=https://payment-service application-cloud.properties: | app.deployment.env=kubernetes app.services.discovery.enabled=true management.endpoints.web.exposure.include=health,metrics,prometheus

Kubernetes Deployment:

apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: myapp:latest ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "prod,cloud,kubernetes" - name: DB_USERNAME valueFrom: secretKeyRef: name: db-secret key: username - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password volumeMounts: - name: config-volume mountPath: /etc/myapp volumes: - name: config-volume configMap: name: myapp-config

Best Practices

1. Organization and Structure

# Best practices for property organization # 1. Use meaningful prefixes app.business.orders.max-per-page=50 app.business.orders.auto-cancel-timeout=24h app.security.jwt.secret=${JWT_SECRET} app.security.jwt.expiration=3600 app.integration.payment.timeout=30s app.integration.payment.retry-attempts=3 # 2. Use profiles for environment-specific values # 3. Externalize sensitive data (use environment variables) # 4. Use consistent naming conventions # 5. Group related properties together

2. Security Considerations

@Component public class SecurityBestPractices { // Never commit secrets to version control // Use environment variables or secret management @Value("${DB_PASSWORD:}") // Default empty, must be provided private String dbPassword; @Value("${API_KEY:}") private String apiKey; // Use configuration classes with validation @ConfigurationProperties(prefix = "app.security") @Validated public class SecurityProperties { @NotBlank private String secretKey; // Getters and setters } }

3. Monitoring and Validation

@Component public class ConfigurationValidator { private final Environment environment; private final ApplicationProperties appProperties; public ConfigurationValidator(Environment environment, ApplicationProperties appProperties) { this.environment = environment; this.appProperties = appProperties; } @PostConstruct public void validateConfiguration() { validateProfiles(); validateRequiredProperties(); logConfiguration(); } private void validateProfiles() { String[] activeProfiles = environment.getActiveProfiles(); if (activeProfiles.length == 0) { logger.warn("No active profiles set. Using default configuration."); } for (String profile : activeProfiles) { if (!isValidProfile(profile)) { throw new IllegalStateException("Invalid profile: " + profile); } } } private void validateRequiredProperties() { if (environment.containsProperty("app.security.jwt.secret") && "default-secret-key".equals(environment.getProperty("app.security.jwt.secret"))) { logger.error("JWT secret is set to default value. This is insecure!"); } } private void logConfiguration() { if (logger.isInfoEnabled()) { logger.info("Active profiles: {}", String.join(", ", environment.getActiveProfiles())); logger.info("Application name: {}", appProperties.getName()); logger.info("Cache enabled: {}", appProperties.getFeatures().isCacheEnabled()); } } private boolean isValidProfile(String profile) { return Set.of("dev", "test", "prod", "local", "cloud").contains(profile); } }

This comprehensive guide covers profile-specific application properties in Java/Spring Boot applications, including configuration, testing, deployment, and best practices for managing environment-specific settings.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper