Home / Spring Boot / Core Concepts / Configuration, Properties and Profiles

Configuration, Properties and Profiles

Spring Boot lets you keep configuration outside the code, so the same jar runs in development, test and production. Learn the property sources, their order, type-safe binding and profiles.

Properties or YAML

server.port=8081
spring.datasource.url=jdbc:postgresql://localhost:5432/shop
spring.datasource.username=app
app.mail.from=noreply@example.com
app.mail.retries=3
server:
  port: 8081
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/shop
    username: app
app:
  mail:
    from: noreply@example.com
    retries: 3

Order of property sources (highest wins)

A value found in a higher source replaces the same key in a lower source. This is why you can override anything at deployment time with an environment variable without rebuilding the jar.

@Value: simple values

@Component
public class MailSender {
    @Value("${app.mail.from}")
    private String from;

    @Value("${app.mail.retries:3}")        // 3 is the default value
    private int retries;
}

@ConfigurationProperties: type-safe binding (recommended)

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties(prefix = "app.mail")
@Validated
public record MailProperties(@Email String from, @Min(1) int retries) { }

@SpringBootApplication
@ConfigurationPropertiesScan            // finds all @ConfigurationProperties classes
public class DemoApplication { }

@Service
public class MailService {
    private final MailProperties props;
    public MailService(MailProperties props) { this.props = props; }
}
Feature@Value@ConfigurationProperties
Best forOne or two valuesA group of related values
Type safetyLowHigh (records, classes, nested objects, lists)
ValidationNoYes with @Validated
Relaxed bindingLimitedFull
IDE helpNoYes (with the configuration processor)

Relaxed binding: the property app.mail-from can be written as app.mailFrom, app.mail_from, or the environment variable APP_MAIL_FROM. All bind to the same field.

Profiles

A profile is a named set of configuration and beans, for example dev, test, prod.

# application.yml  (common)
spring:
  application:
    name: shop
---
# application-dev.yml  or a document in the same file
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:h2:mem:devdb
---
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:postgresql://db.prod:5432/shop
@Configuration
public class MailConfig {
    @Bean
    @Profile("dev")
    public MailClient fakeMailClient() { return new FakeMailClient(); }

    @Bean
    @Profile("prod")
    public MailClient smtpMailClient() { return new SmtpMailClient(); }
}
java -jar app.jar --spring.profiles.active=prod
SPRING_PROFILES_ACTIVE=prod java -jar app.jar
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev

Secrets

Inside the Environment: property sources chain

Environment
  └─ MutablePropertySources  (an ordered list. The FIRST source that has the key wins)
       1. commandLineArgs                    --server.port=9000
       2. servletConfigInitParams / servletContextInitParams
       3. systemProperties                   -Dserver.port=9000
       4. systemEnvironment                  SERVER_PORT=9000
       5. random                             ${random.int}, ${random.uuid}
       6. applicationConfig: [file:./config/application-prod.yml]
       7. applicationConfig: [file:./application.yml]
       8. applicationConfig: [classpath:/config/application.yml]
       9. applicationConfig: [classpath:/application.yml]
      10. @PropertySource sources, defaultProperties
env.getProperty("server.port")  ─► asks the sources one by one, then converts the String to the requested type
${...} placeholders are resolved when values are injected, so one property can refer to another:  app.url=http://${app.host}:${server.port}

Where Boot looks for application.yml (later wins)

spring:
  config:
    import:
      - optional:file:./local-overrides.yml        # not an error if missing
      - optional:configtree:/run/secrets/          # each file name = a property name, file content = value
  profiles:
    group:
      prod: [prod-db, metrics, security-hardening]   # activating "prod" activates all three
    active: ${APP_PROFILE:dev}                       # from an environment variable with a default

Type-safe configuration with nested objects, lists, maps, Duration and DataSize

app:
  storage:
    root: /data/uploads
    max-file-size: 10MB               # DataSize
    retention: 30d                    # Duration
  clients:
    - name: billing
      url: https://billing.internal
      timeout: 2s
    - name: crm
      url: https://crm.internal
      timeout: 5s
  feature-flags:
    new-checkout: true
    beta-search: false
@ConfigurationProperties(prefix = "app")
@Validated
public record AppProperties(Storage storage, List<@Valid Client> clients, Map<String, Boolean> featureFlags) {

    public record Storage(@NotBlank String root, DataSize maxFileSize, Duration retention) { }
    public record Client(@NotBlank String name, @NotBlank String url, @DefaultValue("3s") Duration timeout) { }
}

@Service
public class FlagService {
    private final AppProperties props;
    public FlagService(AppProperties props) { this.props = props; }
    public boolean enabled(String flag) { return props.featureFlags().getOrDefault(flag, false); }
    public long maxBytes() { return props.storage().maxFileSize().toBytes(); }
}
Value in YAMLJava typeNotes
10MB, 512KBDataSizetoBytes(), toMegabytes()
30d, 2h, 500ms, PT15MDurationDefault unit can be set with @DurationUnit
a,b,c or a YAML listList<String>, Set<String>Comma separated works in properties files
key: value blockMap<String, X>Keys keep their case inside quotes
nested blockNested record or classValidated with @Valid on the field

Profiles in tests and deployments

@SpringBootTest
@ActiveProfiles("test")                                        // loads application-test.yml
@TestPropertySource(properties = "app.feature-flags.new-checkout=true")
class CheckoutTest { }

Common mistake: Putting production passwords in application.properties inside the jar. Use environment variables or a secret store, and keep the jar the same for all environments.

Interview questions

What is the difference between application.properties and bootstrap.properties?

application.properties is the normal configuration file. bootstrap.properties was used by Spring Cloud in older versions and is replaced by spring.config.import in new versions.

How do I set the active profile?

Use spring.profiles.active in a file, the SPRING_PROFILES_ACTIVE environment variable or the --spring.profiles.active argument.

Can I have more than one active profile?

Yes. Give a comma separated list, for example spring.profiles.active=prod,metrics.

Which wins: application.yml or an environment variable?

The environment variable. Higher sources in the chain override lower ones, and the packaged application.yml is one of the lowest.

How do I read Docker or Kubernetes secrets that are mounted as files?

Use spring.config.import=optional:configtree:/run/secrets/ so every file becomes a property.