Configuration and Profiles

This section documents Spring Boot 4.1.x and Spring Framework 7.0.x on the Java 17/21+ baseline, as described by the official Spring Boot reference documentation and each sub-project’s own site (Spring Data, Spring for Apache Kafka, Micrometer, Project Reactor, springdoc-openapi, Spring gRPC, and the others listed in this section’s Bibliography) — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Spring Boot and its ecosystem continue to evolve; the examples here target the current 4.1.x / 7.0.x releases.

This section’s bibliography lists the reference material consulted while preparing these pages.

Spring Boot lets an application’s behavior be tuned without touching code, by layering configuration from many sources and binding it into typed Java objects. This page covers where configuration values come from, how to consume them safely, how to vary them per environment with profiles, and how to keep your IDE aware of custom properties.

Externalized configuration sources and precedence

Spring Boot reads configuration from a well-defined, ordered set of property sources and merges them into a single Environment. A property defined in a higher-precedence source overrides the same key from a lower-precedence one. From lowest to highest precedence (abbreviated — see the full, authoritative order in the link below):

  1. application.yml / application.properties packaged inside the jar

  2. Profile-specific files packaged inside the jar (application-<profile>.yml)

  3. application.yml / application.properties outside the packaged jar (e.g. next to it, on disk)

  4. Profile-specific files outside the jar

  5. @ConfigurationProperties beans exposed via @PropertySource

  6. OS environment variables

  7. Java System properties (-D flags)

  8. Command-line arguments

The default configuration file:

# src/main/resources/application.yml
server:
  port: 8080

app:
  greeting: Hello from application.yml
  retry:
    max-attempts: 3

The same property overridden from the environment (environment variable names are upper-cased and use underscores in place of dots/hyphens — relaxed binding, covered below, maps APP_GREETING back to app.greeting):

export APP_GREETING="Hello from an environment variable"
export SERVER_PORT=9090
java -jar target/demo-app-1.0.0.jar

A Java system property takes precedence over an environment variable for the same key:

java -Dapp.greeting="Hello from a system property" -jar target/demo-app-1.0.0.jar

And a command-line argument wins over everything else:

java -jar target/demo-app-1.0.0.jar --app.greeting="Hello from a command-line argument" --server.port=9091

A per-profile file (see Profiles below) sits between the base file and environment variables in the precedence order, so it can override defaults but is itself overridden by anything set on the running host or the command line:

# src/main/resources/application-prod.yml
server:
  port: 8443

app:
  greeting: Hello from the production profile

See Externalized Configuration for the complete, exhaustive precedence order (it also covers .env files, config trees, config data locations via spring.config.import, and randomized values with $\{random.int}).

Type-safe configuration: @ConfigurationProperties vs. @Value

@Value for single, ad-hoc values

@Value injects one property (or a SpEL expression) directly into a field or constructor parameter. It is convenient for a single, rarely-reused setting, but it scatters configuration keys across the codebase and gives no compile-time or validation support:

@Component
public class GreetingService {

    private final String greeting;

    public GreetingService(@Value("${app.greeting}") String greeting) {
        this.greeting = greeting;
    }

    public String greet() {
        return greeting;
    }
}

@ConfigurationProperties for structured, type-safe configuration

@ConfigurationProperties binds a whole tree of related properties onto a dedicated, immutable class. It supports nested objects, collections, Duration/DataSize parsing, and — combined with @Validated — Bean Validation constraints that fail fast at startup if the configuration is wrong:

app:
  retry:
    max-attempts: 5
    backoff: 200ms
  allowed-origins:
    - https://example.com
    - https://admin.example.com
@ConfigurationProperties(prefix = "app.retry")
@Validated
public class RetryProperties {

    @Min(1)
    @Max(10)
    private int maxAttempts = 3;

    @NotNull
    private Duration backoff = Duration.ofMillis(100);

    // getters and setters (or use a Java record, see below)

    public int getMaxAttempts() {
        return maxAttempts;
    }

    public void setMaxAttempts(int maxAttempts) {
        this.maxAttempts = maxAttempts;
    }

    public Duration getBackoff() {
        return backoff;
    }

    public void setBackoff(Duration backoff) {
        this.backoff = backoff;
    }
}

Register the class with @EnableConfigurationProperties on a configuration class (or annotate it @Component directly), then inject it like any other bean:

@Configuration
@EnableConfigurationProperties(RetryProperties.class)
public class AppConfig {
}

@Service
public class OrderClient {

    private final RetryProperties retryProperties;

    public OrderClient(RetryProperties retryProperties) {
        this.retryProperties = retryProperties;
    }

    public int maxAttempts() {
        return retryProperties.getMaxAttempts();
    }
}

Immutable, constructor-binding variants are preferred for new code — a Java record is a natural fit and needs no setters:

@ConfigurationProperties(prefix = "app")
public record AppProperties(
        String greeting,
        @Valid Retry retry,
        List<String> allowedOrigins) {

    public record Retry(@Min(1) @Max(10) int maxAttempts, Duration backoff) {
    }
}

Relaxed binding rules

Spring Boot’s relaxed binding lets a property be written in several equivalent forms across YAML, environment variables, and system properties, so each source can use its own natural casing convention while binding to the same camelCase Java field:

Java property Equivalent external forms

maxAttempts

max-attempts (kebab-case, recommended in .yml), maxAttempts (camelCase), MAXATTEMPTS (upper-case), MAX_ATTEMPTS (environment variable)

allowedOrigins[0]

a YAML list item, or APP_ALLOWEDORIGINS_0_ as an environment variable

Kebab-case is the recommended style inside .yml/.properties files; environment variables must use the upper-snake-case form because most shells forbid dots and hyphens in variable names.

Profiles

Profiles let a set of beans and property values be activated only for a given environment (dev, test, prod, and so on).

Activating a profile

# application.yml
spring:
  application:
    name: demo-app
  profiles:
    active: dev

The active profile can also be set as an environment variable or command-line argument, which — following the precedence order above — overrides whatever is set in application.yml:

export SPRING_PROFILES_ACTIVE=prod
java -jar target/demo-app-1.0.0.jar

# or directly on the command line
java -jar target/demo-app-1.0.0.jar --spring.profiles.active=prod

Profile-specific property files

Any property in application-<profile>.yml is merged on top of the base application.yml only when that profile is active:

# application-dev.yml
logging:
  level:
    com.example.demo: DEBUG

app:
  greeting: Hello from dev
# application-prod.yml
logging:
  level:
    com.example.demo: WARN

app:
  greeting: Hello from prod

A single file can also declare multiple logical documents separated by ---, each restricted to a profile with spring.config.activate.on-profile:

app:
  greeting: Hello from the default document
---
spring:
  config:
    activate:
      on-profile: dev
app:
  greeting: Hello from the dev document
---
spring:
  config:
    activate:
      on-profile: prod
app:
  greeting: Hello from the prod document

@Profile on beans

@Profile restricts a @Component, @Configuration, or @Bean method to specific active profiles, including negation with !:

@Configuration
public class MailConfig {

    @Bean
    @Profile("dev")
    public MailSender devMailSender() {
        return new LoggingMailSender(); // just logs the message, sends nothing
    }

    @Bean
    @Profile("prod")
    public MailSender prodMailSender(MailProperties properties) {
        return new SmtpMailSender(properties);
    }

    @Bean
    @Profile("!test")
    public StartupBanner startupBanner() {
        return new StartupBanner(); // skipped only while the "test" profile is active
    }
}

Multiple profiles can be activated together (--spring.profiles.active=prod,metrics), and profile groups (spring.profiles.group.production=prod,metrics,audit) let one alias expand to several. See Profiles for profile groups, the spring.profiles.include property, and how profiles interact with spring.config.import.

Runtime configuration changes with @RefreshScope

Everything above is bound once, at startup: changing a value in application.yml or the environment has no effect until the process restarts. @RefreshScope (from Spring Cloud Context) removes that restart for selected beans. A @RefreshScope bean is wrapped in a scope proxy; when Spring publishes an EnvironmentChangeEvent, the proxy discards its cached target, so the next call re-creates the bean from whatever the Environment now holds — no JVM restart, no redeployment:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter</artifactId>
</dependency>
@Component
@RefreshScope
@ConfigurationProperties(prefix = "app.retry")
public class RetryProperties {

    private int maxAttempts = 3;
    private Duration backoff = Duration.ofMillis(100);

    // getters and setters
}
management:
  endpoints:
    web:
      exposure:
        include: refresh
# re-binds every @RefreshScope bean from the Environment's current values -- no restart
curl -X POST http://localhost:8080/actuator/refresh

@RefreshScope only reacts to an EnvironmentChangeEvent — it does not, by itself, know that an external value changed. Something has to notice the change and either update the Environment and fire that event, or call /actuator/refresh directly. That "something" is a dynamic-configuration provider:

sequenceDiagram participant Store as External store
(Config Server / ConfigMap / Secrets Manager) participant App as Spring Boot instance participant Bean as @RefreshScope bean Store->>Store: a property changes Store-->>App: provider detects the change
(push, watch, or poll) App->>App: Environment updated +
EnvironmentChangeEvent published App->>Bean: proxy discards cached instance Note over Bean: next access re-creates the bean
from the new Environment values

Spring Cloud Config Server — the platform-independent option

A Config Server serves externalized properties over HTTP from a backing repository (typically a Git repo, so configuration changes are reviewed and versioned like code). Each service is a Config Client that imports its configuration from the server instead of (or alongside) its own application.yml:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
# bootstrap-style import -- fetched from the Config Server at startup and on refresh
spring:
  config:
    import: "configserver:http://config-server:8888"
  application:
    name: orders-service

Calling /actuator/refresh on one instance re-fetches its configuration from the server and refreshes its own @RefreshScope beans — but with several replicas, that means calling the endpoint on every instance individually. Spring Cloud Bus removes that by connecting every instance to a shared message broker (Kafka or RabbitMQ) and broadcasting the refresh instead:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>
# one call, delivered to every instance on the bus -- not just the one that received the request
curl -X POST http://any-instance:8080/actuator/busrefresh

Kubernetes ConfigMaps and Secrets

When the deployment target is Kubernetes, spring-cloud-starter-kubernetes-client-config reads ConfigMap`s and `Secret`s directly as Spring `PropertySource`s and can watch the Kubernetes API for changes, reloading automatically — no `/actuator/refresh call needed:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
spring:
  cloud:
    kubernetes:
      reload:
        enabled: true
        mode: event      # watch the Kubernetes API (default); "polling" re-checks every `period`
        strategy: refresh # re-bind only @ConfigurationProperties/@RefreshScope beans (default);
                           # "restart_context" restarts the whole ApplicationContext instead

The view RBAC role on the pod’s service account is enough to watch ConfigMap changes; watching Secret changes needs a higher role (e.g. edit), since secrets aren’t monitored by default.

AWS Secrets Manager and Parameter Store

On AWS, Spring Cloud AWS imports configuration directly from Secrets Manager (for actual secrets) and Parameter Store (for non-secret dynamic properties):

<dependency>
    <groupId>io.awspring.cloud</groupId>
    <artifactId>spring-cloud-aws-starter-secrets-manager</artifactId>
</dependency>
<dependency>
    <groupId>io.awspring.cloud</groupId>
    <artifactId>spring-cloud-aws-starter-parameter-store</artifactId>
</dependency>
spring:
  config:
    import:
      - "aws-secretsmanager:/orders-service/prod/"
      - "aws-parameterstore:/orders-service/prod/"

Unlike the Kubernetes provider, this integration does not watch AWS for changes on its own: a secret rotation or parameter update in AWS still needs something to trigger the refresh — typically an EventBridge rule reacting to the change and invoking /actuator/refresh on each instance (directly, or via Spring Cloud Bus as above), or a scheduled poll (see Scheduling & ShedLock) that calls it periodically.

Choosing a provider

| Provider | Detects changes | Best fit | Spring Cloud Config (+ Bus) | Manual /actuator/refresh per instance, or one /actuator/busrefresh broadcast via Bus | Platform-independent; configuration reviewed as code in Git | Kubernetes ConfigMaps/Secrets | Automatic (watch or poll) | Deploying to Kubernetes (GKE, EKS, Cloud Run backed by Kubernetes) | AWS Secrets Manager / Parameter Store | Manual trigger (EventBridge or a scheduled poll) | Deploying to AWS, secrets already centralized there

Whichever is chosen, every dynamically-reloadable property should be documented as such, and bound on a @ConfigurationProperties bean annotated @RefreshScope — a plain @Value field is captured once at bean creation and, because there is no proxy to discard, never picks up a refresh.

Configuration metadata for IDE autocompletion

Spring Boot’s annotation processor (spring-boot-configuration-processor) scans @ConfigurationProperties classes at compile time and generates META-INF/spring-configuration-metadata.json, which IDEs such as IntelliJ IDEA and the Spring Tools use to offer autocompletion, inline documentation, and type checking for application.yml keys. Add the processor as an optional dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

For properties the processor cannot discover on its own — values read manually from the Environment, properties supplied by a dependency, or keys you want to enrich with extra defaults and deprecation information — hand-write src/main/resources/META-INF/additional-spring-configuration-metadata.json. Its contents are merged with the generated metadata at build time:

{
  "properties": [
    {
      "name": "app.greeting",
      "type": "java.lang.String",
      "description": "Greeting returned by GreetingService.greet().",
      "defaultValue": "Hello from application.yml"
    },
    {
      "name": "app.retry.max-attempts",
      "type": "java.lang.Integer",
      "description": "Maximum number of retry attempts before giving up.",
      "defaultValue": 3
    }
  ],
  "hints": [
    {
      "name": "app.greeting",
      "providers": [
        {
          "name": "any"
        }
      ]
    }
  ]
}

A property can also be marked deprecated so the IDE flags it and suggests the replacement:

{
  "properties": [
    {
      "name": "app.old-greeting",
      "type": "java.lang.String",
      "description": "Deprecated in favor of app.greeting.",
      "deprecation": {
        "level": "error",
        "replacement": "app.greeting"
      }
    }
  ]
}

See Configuration Metadata for the full JSON schema, including value hints, group metadata, and how the processor infers types and defaults from @ConfigurationProperties classes.

Where configuration and profiles fit next

The externalized-configuration and profile mechanisms on this page underpin almost every other topic in this reference — data source URLs and credentials, Kafka bootstrap servers, cache TTLs, and observability endpoints are all ordinary @ConfigurationProperties-bound values layered the same way. See Core Concepts for how auto-configuration consumes these same properties, and Spring Data Overview for the first concrete example: per-profile datasource configuration.