Spring Boot Topic
1. Spring Boot Profiling
1.1 What are Profiles?
Profiles provide a way to segregate parts of your application configuration and make it available only in certain environments.
1.2 Profile-Specific Configuration Files
File Naming Convention: application-{profile}.properties or application-{profile}.yml
src/main/resources/
├── application.properties # Default configuration
├── application-dev.properties # Development profile
├── application-test.properties # Testing profile
├── application-prod.properties # Production profile
└── application-uat.properties # UAT profile
1.3 Properties 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
logging.level.root=INFO
application-dev.properties:
# Development specific configuration
server.port=9090
spring.datasource.url=jdbc:h2:mem:devdb
spring.h2.console.enabled=true
spring.jpa.show-sql=true
logging.level.com.example=DEBUG
logging.level.org.hibernate.SQL=DEBUG
app.debug.enabled=true
app.feature.new-payment=true
application-prod.properties:
# Production specific configuration
server.port=8443
spring.datasource.url=jdbc:mysql://prod-db:3306/myapp
spring.jpa.show-sql=false
logging.level.root=WARN
management.endpoints.web.exposure.include=health,metrics
app.debug.enabled=false
app.feature.new-payment=false
security.require-ssl=true
1.4 YAML Configuration with Profiles
application.yml:
# Default configuration
app:
name: MyApplication
version: 1.0.0
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:defaultdb
logging:
level:
root: INFO
---
# Development profile
spring:
config:
activate:
on-profile: dev
server:
port: 9090
spring:
datasource:
url: jdbc:h2:mem:devdb
h2:
console:
enabled: true
jpa:
show-sql: true
logging:
level:
com.example: DEBUG
org.hibernate.SQL: DEBUG
app:
debug:
enabled: true
feature:
new-payment: true
---
# Production profile
spring:
config:
activate:
on-profile: prod
server:
port: 8443
spring:
datasource:
url: jdbc:mysql://prod-db:3306/myapp
jpa:
show-sql: false
logging:
level:
root: WARN
management:
endpoints:
web:
exposure:
include: health,metrics
app:
debug:
enabled: false
feature:
new-payment: false
security:
require-ssl: true
1.5 Activating Profiles
Multiple Ways to Activate Profiles:
1. Command Line:
java -jar myapp.jar --spring.profiles.active=dev,debug
2. Environment Variable:
export SPRING_PROFILES_ACTIVE=prod
3. System Property:
java -Dspring.profiles.active=test -jar myapp.jar
4. In application.properties:
spring.profiles.active=dev
5. Programmatically:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
// Set active profiles
app.setAdditionalProfiles("dev", "local");
app.run(args);
}
}
1.6 Profile-Specific Beans
Using @Profile Annotation:
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:h2:mem:devdb");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://prod-db:3306/myapp");
dataSource.setUsername("prod-user");
dataSource.setPassword("secure-password");
dataSource.setMaximumPoolSize(20);
return dataSource;
}
@Bean
@Profile("!prod") // All profiles except prod
public DataSource testDataSource() {
// Test configuration
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Service
@Profile({"dev", "test"})
public class MockEmailService implements EmailService {
// Mock implementation for non-production
}
@Service
@Profile("prod")
public class ProductionEmailService implements EmailService {
// Real implementation for production
}
1.7 Conditional Configuration with Profiles
@Configuration
@ConditionalOnProperty(name = "app.feature.new-payment", havingValue = "true")
@ConditionalOnProfile("dev")
public class NewPaymentFeatureConfig {
@Bean
public PaymentService newPaymentService() {
return new NewPaymentService();
}
}
@Configuration
@ConditionalOnMissingBean(PaymentService.class)
public class LegacyPaymentFeatureConfig {
@Bean
public PaymentService legacyPaymentService() {
return new LegacyPaymentService();
}
}
1.8 Testing with Profiles
@SpringBootTest
@ActiveProfiles({"test", "integration"})
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Test
void shouldWorkInTestProfile() {
// Test implementation
}
}
@DataJpaTest
@ActiveProfiles("test")
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void shouldUseTestDatabase() {
// Test with test profile configuration
}
}
2. @ConfigurationProperties - Detailed Guide
2.1 Introduction to @ConfigurationProperties
@ConfigurationProperties provides type-safe configuration binding from property files to Java objects.
2.2 Basic Usage
Properties File:
# application.properties
app.database.url=jdbc:mysql://localhost:3306/mydb
app.database.username=admin
app.database.password=secret123
app.database.pool-size=15
app.database.timeout=30000
app.database.retry-enabled=true
app.database.connection-pool.initial-size=5
app.database.connection-pool.max-size=20
app.database.connection-pool.timeout=60000
Configuration Properties Class:
@Component
@ConfigurationProperties(prefix = "app.database")
@Validated
public class DatabaseProperties {
@NotBlank
private String url;
@NotBlank
private String username;
@NotBlank
private String password;
@Min(1)
@Max(100)
private int poolSize = 10;
@Min(1000)
private long timeout = 30000;
private boolean retryEnabled = false;
private ConnectionPool connectionPool = new ConnectionPool();
// Nested configuration class
public static class ConnectionPool {
private int initialSize = 2;
private int maxSize = 10;
private long timeout = 30000;
// Getters and setters
public int getInitialSize() { return initialSize; }
public void setInitialSize(int initialSize) { this.initialSize = initialSize; }
// ... other getters/setters
}
// Getters and setters
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getPoolSize() { return poolSize; }
public void setPoolSize(int poolSize) { this.poolSize = poolSize; }
public long getTimeout() { return timeout; }
public void setTimeout(long timeout) { this.timeout = timeout; }
public boolean isRetryEnabled() { return retryEnabled; }
public void setRetryEnabled(boolean retryEnabled) { this.retryEnabled = retryEnabled; }
public ConnectionPool getConnectionPool() { return connectionPool; }
public void setConnectionPool(ConnectionPool connectionPool) { this.connectionPool = connectionPool; }
}
2.3 Using with @ConfigurationPropertiesScan
@SpringBootApplication
@ConfigurationPropertiesScan("com.example.config")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// No need for @Component annotation
@ConfigurationProperties(prefix = "app.database")
@Validated
public class DatabaseProperties {
// Properties and getters/setters
}
2.4 Complex Types and Collections
Properties with Lists and Maps:
# application.properties
app.notification.email.recipients=admin@example.com,user@example.com,support@example.com
app.notification.email.cc=manager@example.com
app.notification.email.bcc=audit@example.com
app.notification.email.subject-prefix=[APP]
app.security.allowed-ips=192.168.1.100,192.168.1.101,10.0.0.50
app.security.blocked-countries=RU,CN,KR
app.services.timeouts.auth=5000
app.services.timeouts.payment=30000
app.services.timeouts.notification=10000
app.feature.flags.new-ui=true
app.feature.flags.advanced-search=false
app.feature.flags.export-pdf=true
Java Configuration Class:
@Component
@ConfigurationProperties(prefix = "app.notification.email")
public class EmailProperties {
private List<String> recipients = new ArrayList<>();
private List<String> cc = new ArrayList<>();
private List<String> bcc = new ArrayList<>();
private String subjectPrefix = "";
// Getters and setters
public List<String> getRecipients() { return recipients; }
public void setRecipients(List<String> recipients) { this.recipients = recipients; }
// ... other getters/setters
}
@Component
@ConfigurationProperties(prefix = "app.security")
public class SecurityProperties {
private List<String> allowedIps = new ArrayList<>();
private Set<String> blockedCountries = new HashSet<>();
private Map<String, String> headers = new HashMap<>();
// Getters and setters
public List<String> getAllowedIps() { return allowedIps; }
public void setAllowedIps(List<String> allowedIps) { this.allowedIps = allowedIps; }
public Set<String> getBlockedCountries() { return blockedCountries; }
public void setBlockedCountries(Set<String> blockedCountries) { this.blockedCountries = blockedCountries; }
}
@Component
@ConfigurationProperties(prefix = "app.services")
public class ServiceProperties {
private Map<String, Integer> timeouts = new HashMap<>();
private Map<String, Boolean> featureFlags = new HashMap<>();
// Getters and setters
public Map<String, Integer> getTimeouts() { return timeouts; }
public void setTimeouts(Map<String, Integer> timeouts) { this.timeouts = timeouts; }
public Map<String, Boolean> getFeatureFlags() { return featureFlags; }
public void setFeatureFlags(Map<String, Boolean> featureFlags) { this.featureFlags = featureFlags; }
}
2.5 Validation with @Validated
@Component
@ConfigurationProperties(prefix = "app.validation")
@Validated
public class ValidationProperties {
@NotNull
@Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$")
private String adminEmail;
@Min(1)
@Max(86400)
private int sessionTimeout = 3600;
@DecimalMin("0.0")
@DecimalMax("1.0")
private double successThreshold = 0.8;
@NotEmpty
private List<@Email String> notificationEmails = new ArrayList<>();
@Valid
private CacheSettings cache = new CacheSettings();
public static class CacheSettings {
@Min(1)
private int size = 1000;
@Min(60)
private int ttl = 3600;
// Getters and setters
public int getSize() { return size; }
public void setSize(int size) { this.size = size; }
public int getTtl() { return ttl; }
public void setTtl(int ttl) { this.ttl = ttl; }
}
// Getters and setters
public String getAdminEmail() { return adminEmail; }
public void setAdminEmail(String adminEmail) { this.adminEmail = adminEmail; }
public int getSessionTimeout() { return sessionTimeout; }
public void setSessionTimeout(int sessionTimeout) { this.sessionTimeout = sessionTimeout; }
public double getSuccessThreshold() { return successThreshold; }
public void setSuccessThreshold(double successThreshold) { this.successThreshold = successThreshold; }
public List<String> getNotificationEmails() { return notificationEmails; }
public void setNotificationEmails(List<String> notificationEmails) { this.notificationEmails = notificationEmails; }
public CacheSettings getCache() { return cache; }
public void setCache(CacheSettings cache) { this.cache = cache; }
}
2.6 Profile-Specific @ConfigurationProperties
@Component
@ConfigurationProperties(prefix = "app.payment")
@Profile("prod")
public class ProductionPaymentProperties {
private String apiKey;
private String secret;
private String endpoint = "https://api.payment.com/v1";
private double timeout = 30.0;
// Production-specific getters and setters
}
@Component
@ConfigurationProperties(prefix = "app.payment")
@Profile("dev")
public class DevelopmentPaymentProperties {
private String apiKey = "test-key";
private String secret = "test-secret";
private String endpoint = "https://sandbox.payment.com/v1";
private double timeout = 60.0;
// Development-specific getters and setters
}
2.7 Using @ConfigurationProperties in Services
@Service
public class PaymentService {
private final PaymentProperties paymentProperties;
private final DatabaseProperties databaseProperties;
public PaymentService(PaymentProperties paymentProperties,
DatabaseProperties databaseProperties) {
this.paymentProperties = paymentProperties;
this.databaseProperties = databaseProperties;
}
public void processPayment(PaymentRequest request) {
// Use configuration properties
if (paymentProperties.isRetryEnabled()) {
processWithRetry(request, paymentProperties.getMaxRetries());
} else {
processSingle(request);
}
}
private void processWithRetry(PaymentRequest request, int maxRetries) {
// Implementation with retry logic
for (int i = 0; i < maxRetries; i++) {
try {
processSingle(request);
break;
} catch (Exception e) {
if (i == maxRetries - 1) throw e;
}
}
}
}
3. @Value Annotation - Detailed Guide
3.1 Basic @Value Usage
@Value is used for injecting values from property files, system properties, environment variables, or SpEL expressions.
Simple Property Injection:
@Service
public class SimpleService {
@Value("${app.name:DefaultAppName}")
private String appName;
@Value("${app.version}")
private String appVersion;
@Value("${server.port:8080}")
private int serverPort;
@Value("${app.feature.enabled:false}")
private boolean featureEnabled;
}
3.2 Default Values
@Service
public class DefaultValueService {
// With default values
@Value("${app.timeout:5000}")
private int timeout;
@Value("${app.retry.count:3}")
private int retryCount;
@Value("${app.notification.email:admin@example.com}")
private String notificationEmail;
@Value("${app.debug.mode:false}")
private boolean debugMode;
}
3.3 System Properties and Environment Variables
@Service
public class SystemPropertyService {
// System properties
@Value("#{systemProperties['java.home']}")
private String javaHome;
@Value("#{systemProperties['user.name']}")
private String userName;
@Value("#{systemProperties['user.timezone']}")
private String timezone;
// Environment variables
@Value("#{environment['HOME']}")
private String homeDirectory;
@Value("#{environment['PATH']}")
private String path;
@Value("${JAVA_HOME:#{systemProperties['java.home']}}")
private String javaHomeWithFallback;
}
3.4 SpEL (Spring Expression Language) Expressions
@Service
public class SpelExpressionService {
// Mathematical expressions
@Value("#{T(java.lang.Math).random() * 100.0}")
private double randomPercentage;
@Value("#{100 + 50}")
private int calculatedValue;
@Value("#{${app.base.rate} * ${app.multiplier}}")
private double calculatedRate;
// String operations
@Value("#{'Hello ' + 'World'}")
private String greeting;
@Value("#{${app.server.url} + '/api/v1'}")
private String apiBaseUrl;
// Boolean expressions
@Value("#{${app.feature.enabled} and ${app.user.count} > 100}")
private boolean shouldEnableAdvancedFeature;
// Collection operations
@Value("#{${app.allowed.roles}.split(',')}")
private List<String> allowedRoles;
@Value("#{T(java.util.Arrays).asList('admin', 'user', 'guest')}")
private List<String> defaultRoles;
// Bean references
@Value("#{@environment.getProperty('app.name')}")
private String appNameFromBean;
@Value("#{systemProperties['user.country'] ?: 'US'}")
private String defaultCountry;
}
3.5 Complex SpEL Examples
@Service
public class AdvancedSpelService {
// Conditional values
@Value("#{${app.production} ? 'production' : 'development'}")
private String environmentType;
@Value("#{${app.user.count} > 1000 ? 'LARGE' : 'SMALL'}")
private String scaleType;
// Date and time expressions
@Value("#{T(java.time.LocalDateTime).now()}")
private LocalDateTime currentTime;
@Value("#{T(java.time.format.DateTimeFormatter).ofPattern('yyyy-MM-dd HH:mm:ss')}")
private DateTimeFormatter dateFormatter;
// Method calls
@Value("#{T(java.util.UUID).randomUUID().toString()}")
private String randomUuid;
@Value("#{T(com.example.util.StringUtils).capitalize('hello')}")
private String capitalizedString;
// Collection filtering
@Value("#{${app.numbers}.?[#this > 10]}")
private List<Integer> filteredNumbers;
// Map operations
@Value("#{${app.settings}['timeout']}")
private Integer timeoutSetting;
@Value("#{${app.users}.![value.name]}")
private List<String> userNames;
}
3.6 Lists and Maps with @Value
Properties File:
# application.properties
app.numbers=1,2,3,4,5,10,20,30
app.colors=red,green,blue,yellow
app.settings.timeout=5000
app.settings.retry=3
app.settings.enabled=true
app.users.admin.name=John
app.users.admin.role=ADMIN
app.users.user.name=Jane
app.users.user.role=USER
Java Service:
@Service
public class CollectionValueService {
// Simple list from comma-separated values
@Value("${app.numbers}")
private List<Integer> numbers;
@Value("#{'${app.colors}'.split(',')}")
private List<String> colors;
@Value("#{'${app.colors}'.split(',')}.?[#this.startsWith('b')]")
private List<String> colorsStartingWithB;
// Map from properties
@Value("#{${app.settings}}")
private Map<String, String> settings;
// Complex map structure
@Value("#{${app.users}}")
private Map<String, Map<String, String>> users;
public void processCollections() {
System.out.println("Numbers: " + numbers);
System.out.println("Colors: " + colors);
System.out.println("Settings: " + settings);
System.out.println("Users: " + users);
}
}
3.7 Profile-Specific @Value Usage
@Service
public class ProfileSpecificService {
@Value("${app.database.url}")
private String databaseUrl;
@Value("${app.api.key:default-key}")
private String apiKey;
@Value("${app.debug:false}")
private boolean debugMode;
@PostConstruct
public void init() {
if (debugMode) {
System.out.println("Database URL: " + databaseUrl);
System.out.println("API Key: " + apiKey);
}
}
// Using in methods
public String getServiceInfo(@Value("${app.service.name}") String serviceName) {
return "Service: " + serviceName + ", Database: " + databaseUrl;
}
}
3.8 Validation with @Value
@Service
@Validated
public class ValidatedValueService {
@Value("${app.port:8080}")
@Min(1024)
@Max(65535)
private int serverPort;
@Value("${app.email}")
@Email
private String adminEmail;
@Value("${app.percentage:0.5}")
@DecimalMin("0.0")
@DecimalMax("1.0")
private double successPercentage;
@Value("${app.name}")
@Size(min = 3, max = 50)
private String appName;
}
4. Comparison: @ConfigurationProperties vs @Value
4.1 When to Use Which?
| Aspect | @ConfigurationProperties |
@Value |
|---|---|---|
| Use Case | Complex configuration with multiple related properties | Simple, individual property injection |
| Type Safety | Strong type safety with validation | Basic type conversion |
| Relaxed Binding | Yes (app.port = appPort = APP_PORT) | No |
| Validation | Full JSR-303 validation support | Limited validation |
| Immutable | Can be made immutable with @ConstructorBinding |
No |
| IDE Support | Excellent with metadata | Basic |
| Collections | Full support for Lists, Maps, Sets | Limited with SpEL |
| Nested Properties | Excellent support | Limited |
4.2 Best Practices
Use @ConfigurationProperties when:
-
You have groups of related properties
-
You need type safety and validation
-
You want IDE auto-completion
-
You have complex nested configurations
Use @Value when:
-
You need to inject single, unrelated properties
-
You want to use SpEL expressions
-
You need simple default values
-
You're injecting system properties or environment variables
4.3 Combined Usage Example
@Configuration
@EnableConfigurationProperties({DatabaseProperties.class, SecurityProperties.class})
public class AppConfig {
@Bean
public DataSource dataSource(DatabaseProperties dbProps,
@Value("${app.datasource.driver:com.mysql.cj.jdbc.Driver}") String driverClass) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(dbProps.getUrl());
dataSource.setUsername(dbProps.getUsername());
dataSource.setPassword(dbProps.getPassword());
dataSource.setDriverClassName(driverClass);
dataSource.setMaximumPoolSize(dbProps.getPoolSize());
return dataSource;
}
@Bean
@Profile("dev")
public String devInfo(@Value("${app.debug.details:false}") boolean debugDetails,
DatabaseProperties dbProps) {
if (debugDetails) {
return String.format("Dev DB: %s, Pool: %d",
dbProps.getUrl(), dbProps.getPoolSize());
}
return "Development mode";
}
}
This comprehensive guide covers all aspects of Spring Boot configuration management with practical examples and best practices for different scenarios.
Comments (0)
No comments yet. Be the first to comment!