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):
-
application.yml/application.propertiespackaged inside the jar -
Profile-specific files packaged inside the jar (
application-<profile>.yml) -
application.yml/application.propertiesoutside the packaged jar (e.g. next to it, on disk) -
Profile-specific files outside the jar
-
@ConfigurationPropertiesbeans exposed via@PropertySource -
OS environment variables
-
Java System properties (
-Dflags) -
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 |
|---|---|
|
|
|
a YAML list item, or |
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:
(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.