Menu

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!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?