StackWise
Spring Boot 4Core Concepts

Configuration, Properties & Profiles

application.yml, property precedence, type-safe @ConfigurationProperties with records and validation, profiles and secrets.

beginner 3 min readLesson 5 of 23

Externalised configuration

Boot reads configuration from many sources and merges them into one Environment. The same jar runs in every environment; only the configuration changes.

# src/main/resources/application.yml
spring:
  application:
    name: shop
  datasource:
    url: jdbc:postgresql://localhost:5432/shop
    username: shop
server:
  port: 8080
payments:
  base-url: https://api.payments.example
  timeout: 3s
  max-retries: 3
  supported-currencies: [GBP, EUR, USD]

Precedence (highest wins)

  1. Command-line arguments — --server.port=9090
  2. SPRING_APPLICATION_JSON
  3. Java system properties — -Dserver.port=9090
  4. OS environment variables — SERVER_PORT=9090
  5. Profile-specific files outside the jar, then inside — application-prod.yml
  6. application.yml / .properties outside the jar, then inside
  7. @PropertySource and defaults

Relaxed binding maps payments.max-retries, PAYMENTS_MAXRETRIES, payments.maxRetries to the same property — which is how environment variables in Kubernetes override YAML.

Type-safe properties with records

Prefer @ConfigurationProperties over scattering @Value("${...}"): it’s typed, grouped, validated, documented by IDE metadata, and testable.

@Validated
@ConfigurationProperties("payments")
public record PaymentProperties(
        @NotNull URI baseUrl,
        @DefaultValue("5s") Duration timeout,
        @Min(0) @Max(10) int maxRetries,
        @DefaultValue({"GBP"}) Set<Currency> supportedCurrencies) {
}
@SpringBootApplication
@ConfigurationPropertiesScan          // registers every @ConfigurationProperties type
public class ShopApplication { ... }

@Service
class PaymentClient {
    PaymentClient(PaymentProperties props, RestClient.Builder builder) {
        this.client = builder.baseUrl(props.baseUrl().toString()).build();
        this.timeout = props.timeout();
    }
}

Boot converts strings into Duration (3s, 500ms, PT1M), DataSize (10MB), URI, enums, Currency, lists and maps. With @Validated, a bad value fails at startup with a clear message instead of in production at 3 a.m.

@Value — for one-off values

@Value("${feature.new-checkout:false}") boolean newCheckout;       // with a default
@Value("#{${limits.per-tier}}") Map<String, Integer> limits;       // SpEL

Profiles

A profile is a named set of configuration and beans, activated per environment.

# application.yml — shared defaults
spring:
  jpa:
    open-in-view: false
---
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:h2:mem:shop
logging:
  level:
    org.hibernate.SQL: debug
---
spring:
  config:
    activate:
      on-profile: prod
server:
  shutdown: graceful

Activate with SPRING_PROFILES_ACTIVE=prod, --spring.profiles.active=prod, or in tests with @ActiveProfiles("test"). Beans can be profile-specific:

@Configuration
class StorageConfig {
    @Bean @Profile("!prod")  FileStore localStore() { return new LocalFileStore(Path.of("/tmp/uploads")); }
    @Bean @Profile("prod")   FileStore s3Store(S3Client s3) { return new S3FileStore(s3, "shop-uploads"); }
}

Profile groups bundle several: spring.profiles.group.prod=prod-db,prod-observability.

Importing more config and secrets

spring:
  config:
    import:
      - optional:file:./config/local.yml        # developer overrides, not committed
      - optional:configtree:/run/secrets/        # Kubernetes/Docker secrets mounted as files

Never commit secrets. Inject them via environment variables, mounted secret files (configtree:), or a secrets manager integration (Vault, AWS Secrets Manager, Azure Key Vault).

Inspecting configuration

  • /actuator/env — every property source and value (sanitised)
  • /actuator/configprops — all @ConfigurationProperties beans and their bound values
Finished this lesson?

Mark it complete to track your progress through Spring Boot 4.