Spring Boot Configuration Properties
@ConfigurationProperties is a Spring Boot feature that binds external configuration values, from application.properties, YAML files, environment variables or command-line arguments, to strongly typed Java objects.
In this guide you will build the configuration for an online shop step by step: a simple properties class, immutable records, nested objects, lists and maps, durations and sizes, default values, validation, environment-specific values, IDE auto-completion and tests.
1. What Is @ConfigurationProperties?
Every real application has settings that change between environments: database addresses, API keys, timeouts, feature switches. These belong outside the code, in configuration files or environment variables.
@ConfigurationProperties lets you map a group of related settings that share a common prefix to one Java class. Instead of reading values one at a time, Spring Boot fills the whole object for you, converting each value to the right type.
Think of it like a form. Reading settings one by one with @Value is like copying single answers from a form. @ConfigurationProperties reads the whole form into a neat object that you can pass around.
2. @Value vs @ConfigurationProperties
Spring offers two ways to read configuration. Both are useful, but for different jobs.
@Service
public class CheckoutService {
@Value("${shop.payment.timeout}")
private Duration paymentTimeout;
@Value("${shop.payment.max-retries}")
private int maxRetries;
@Value("${shop.free-shipping-over}")
private BigDecimal freeShippingOver;
}
This works, but the property names are scattered across the code as strings, a typo is only found when the application starts, and nothing tells you which settings belong together.
| Aspect | @Value | @ConfigurationProperties |
|---|---|---|
| Best for | One or two single values | A group of related values |
| Where names live | Spread across classes as strings | In one class, in one place |
| Type conversion | Yes | Yes, including nested objects, lists and maps |
Relaxed binding (max-retries, maxRetries, MAX_RETRIES) | Limited | Full support |
Validation with @NotBlank, @Min and similar | No | Yes |
| IDE auto-completion for your own properties | No | Yes, with the configuration processor |
SpEL expressions such as #{...} | Yes | No |
| Easy to unit test | Harder | Easy: it is a plain object |
Rule of thumb: use @ConfigurationProperties for anything that has more than one or two related settings, and keep @Value for the occasional single value.
3. Your First Configuration Properties Class
Start with the settings in application.properties:
shop.name=Book Corner
shop.support-email=help@bookcorner.example
shop.max-items-per-order=20
Now create a class whose fields match the names after the shop. prefix. In this classic JavaBean style, Spring creates the object and calls the setters:
package com.example.shop.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "shop")
public class ShopProperties {
private String name;
private String supportEmail; // bound from shop.support-email
private int maxItemsPerOrder; // bound from shop.max-items-per-order
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSupportEmail() {
return supportEmail;
}
public void setSupportEmail(String supportEmail) {
this.supportEmail = supportEmail;
}
public int getMaxItemsPerOrder() {
return maxItemsPerOrder;
}
public void setMaxItemsPerOrder(int maxItemsPerOrder) {
this.maxItemsPerOrder = maxItemsPerOrder;
}
}
Notice how support-email in the file becomes supportEmail in Java. Spring Boot matches the two automatically; you will learn more in section 8. It also converts "20" into the int 20.
The class is not used yet: Spring Boot must be told to create it. That is the next step.
4. Registering Your Properties Class
There are three ways to turn a @ConfigurationProperties class into a bean. Pick one.
4.1 Scan for them (recommended)
Add @ConfigurationPropertiesScan to your main class. Spring Boot then finds every @ConfigurationProperties class in that package and below:
package com.example.shop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}
4.2 List them explicitly
@EnableConfigurationProperties registers only the classes you name. This is common in libraries and auto-configuration, where you want full control:
@Configuration
@EnableConfigurationProperties(ShopProperties.class)
public class ShopConfig {
}
4.3 Add @Component
Putting @Component on a JavaBean-style properties class also works, because component scanning picks it up. It does not work for records or constructor-bound classes, so the first two options are better habits.
5. Immutable Properties with Records
Configuration should not change while the application runs. A Java record gives you an immutable properties class in a few lines: no setters, no getters to write, and nobody can change the values by accident.
package com.example.shop.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "shop")
public record ShopProperties(
String name,
String supportEmail,
int maxItemsPerOrder) {
}
Spring Boot sees that the record has a single constructor and passes the values to it. This is called constructor binding. You read values with the record’s accessor methods, such as properties.name().
A normal class works the same way when it has exactly one constructor with parameters. If a class has several constructors, put @ConstructorBinding on the one Spring should use:
package com.example.shop.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
@ConfigurationProperties(prefix = "shop.payment")
public class PaymentProperties {
private final String provider;
private final int maxRetries;
@ConstructorBinding
public PaymentProperties(String provider, int maxRetries) {
this.provider = provider;
this.maxRetries = maxRetries;
}
// Used by tests, not by Spring
public PaymentProperties(String provider) {
this(provider, 3);
}
public String getProvider() {
return provider;
}
public int getMaxRetries() {
return maxRetries;
}
}
@ConstructorBinding was placed on the class and lived in a different package. Since Spring Boot 3.0 it is only needed on a constructor, and only when there is more than one.6. Nested Objects, Lists and Maps
Real configuration has structure. Records can contain other records, lists and maps, and Spring Boot fills all of them.
shop.name=Book Corner
shop.support-email=help@bookcorner.example
# Nested object
shop.payment.provider=stripe
shop.payment.max-retries=3
# List of simple values
shop.currencies=EUR,USD,GBP
# List of objects
shop.warehouses[0].city=Berlin
shop.warehouses[0].capacity=5000
shop.warehouses[1].city=Madrid
shop.warehouses[1].capacity=3000
# Map: country code -> tax rate
shop.tax-rates.de=0.19
shop.tax-rates.fr=0.20
package com.example.shop.config;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "shop")
public record ShopProperties(
String name,
String supportEmail,
Payment payment,
List<String> currencies,
List<Warehouse> warehouses,
Map<String, BigDecimal> taxRates) {
public record Payment(String provider, int maxRetries) {
}
public record Warehouse(String city, int capacity) {
}
}
Keeping the nested records inside ShopProperties shows clearly that they belong to it. A comma-separated value such as EUR,USD,GBP is split into a list automatically.
7. Using YAML
For nested settings, YAML is often easier to read, because the structure is visible from the indentation. Here is the same configuration in application.yml:
shop:
name: Book Corner
support-email: help@bookcorner.example
payment:
provider: stripe
max-retries: 3
currencies:
- EUR
- USD
- GBP
warehouses:
- city: Berlin
capacity: 5000
- city: Madrid
capacity: 3000
tax-rates:
de: 0.19
fr: 0.20
The Java class does not change at all. Choose whichever format your team prefers, and avoid having both application.properties and application.yml in the same project, because it becomes hard to know which value wins.
version: 1.10 is read as the number 1.1. Put such values in quotes: version: "1.10".8. Relaxed Binding and Environment Variables
Spring Boot is flexible about how property names are written. All of these bind to the field supportEmail under the shop prefix:
| Written as | Style | Typical place |
|---|---|---|
shop.support-email | Kebab case (recommended) | .properties and .yml files |
shop.supportEmail | Camel case | Files (works, but less common) |
shop.support_email | Underscore | Files (works, but less common) |
SHOP_SUPPORTEMAIL | Upper case with underscores | Environment variables |
This is called relaxed binding. It is what lets you override any setting with an environment variable, which is how most cloud platforms and containers pass configuration.
To turn a property name into an environment variable:
- Replace each dot (
.) with an underscore (_). - Remove any dashes (
-). - Convert everything to upper case.
# Overrides shop.payment.max-retries
export SHOP_PAYMENT_MAXRETRIES=5
# Overrides shop.support-email
export SHOP_SUPPORTEMAIL=support@bookcorner.example
java -jar shop.jar
You can also override a value from the command line:
java -jar shop.jar --shop.payment.max-retries=5
prefix itself must be in lower-case kebab case, such as "shop" or "shop.payment". A prefix like "myShop" is rejected when the application starts.9. Durations, Data Sizes and Enums
Spring Boot converts text into many useful types for you. Three are especially handy.
9.1 Durations
Use java.time.Duration for timeouts and intervals, and write the value with a unit:
shop.payment.timeout=10s
shop.cart.expiry=2h
shop.cache.refresh-interval=500ms
| Suffix | Unit | Suffix | Unit |
|---|---|---|---|
ns | nanoseconds | s | seconds |
us | microseconds | m | minutes |
ms | milliseconds | h | hours |
d | days |
A plain number without a unit, such as timeout=10, is read as milliseconds. To make plain numbers mean seconds, add @DurationUnit(ChronoUnit.SECONDS) to the field or parameter. Writing the unit is clearer, though.
9.2 Data sizes
Use DataSize for file sizes and memory limits:
shop.uploads.max-image-size=5MB
shop.uploads.max-total-size=1GB
Supported units are B, KB, MB, GB and TB. A plain number means bytes, unless you add @DataSizeUnit.
9.3 Enums
Enum values are matched without caring about case or dashes, so stripe, STRIPE and Stripe all bind to PaymentProvider.STRIPE.
Here is the payment record using all three types:
import java.time.Duration;
import org.springframework.util.unit.DataSize;
public record Payment(
PaymentProvider provider, // stripe -> PaymentProvider.STRIPE
int maxRetries,
Duration timeout, // 10s -> Duration.ofSeconds(10)
DataSize maxReceiptSize) { // 2MB -> DataSize.ofMegabytes(2)
}
public enum PaymentProvider {
STRIPE, PAYPAL, BANK_TRANSFER
}
10. Default Values
When a property is missing from the configuration, an object field is null and a number is 0. Usually you want a sensible default instead.
10.1 Defaults in a record: @DefaultValue
package com.example.shop.config;
import java.time.Duration;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
@ConfigurationProperties(prefix = "shop")
public record ShopProperties(
String name,
@DefaultValue("20") int maxItemsPerOrder,
@DefaultValue({"EUR", "USD"}) List<String> currencies,
@DefaultValue Payment payment) { // creates Payment even if no shop.payment.* is set
public record Payment(
@DefaultValue("3") int maxRetries,
@DefaultValue("10s") Duration timeout) {
}
}
The empty @DefaultValue on payment is worth remembering: without it, the whole Payment object would be null if no shop.payment.* property is set at all.
10.2 Defaults in a JavaBean class
In a class with setters, simply give the field an initial value. It is replaced only when the property is set:
private int maxItemsPerOrder = 20;
private Duration paymentTimeout = Duration.ofSeconds(10);
private List<String> currencies = new ArrayList<>(List.of("EUR", "USD"));
11. Validating Properties at Startup
A missing database address or a negative timeout should stop the application at startup, not cause a confusing error hours later. Add the validation starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Then put @Validated on the class and use the standard jakarta.validation annotations:
package com.example.shop.config;
import java.time.Duration;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.validation.annotation.Validated;
@Validated
@ConfigurationProperties(prefix = "shop")
public record ShopProperties(
@NotBlank String name,
@Email @NotBlank String supportEmail,
@Min(1) @Max(100) @DefaultValue("20") int maxItemsPerOrder,
@Valid @DefaultValue Payment payment) { // @Valid also checks Payment
public record Payment(
@NotBlank String provider,
@Min(0) @Max(10) int maxRetries,
@NotNull @DefaultValue("10s") Duration timeout) {
}
}
If a value breaks a rule, the application refuses to start and tells you exactly what is wrong:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target com.example.shop.config.ShopProperties failed:
Property: shop.supportEmail
Value: "help-at-bookcorner"
Reason: must be a well-formed email address
Action:
Update your application's configuration
javax.validation. Spring Boot 3 and later use the jakarta.validation package instead. Learn more in the Spring Boot Validation guide.12. Using Properties in Your Code
A properties class is a normal bean, so inject it through the constructor wherever you need it. This example uses the validated ShopProperties record from section 11:
package com.example.shop.order;
import org.springframework.stereotype.Service;
import com.example.shop.config.ShopProperties;
@Service
public class CheckoutService {
private final ShopProperties shop;
public CheckoutService(ShopProperties shop) {
this.shop = shop;
}
public void checkLimits(Cart cart) {
if (cart.itemCount() > shop.maxItemsPerOrder()) {
throw new IllegalArgumentException(
"You can order at most " + shop.maxItemsPerOrder() + " items");
}
}
public String helpMessage() {
return "Questions about your order? Write to " + shop.supportEmail();
}
}
You can also inject it into a @Bean method to configure other objects, such as an HTTP client:
@Configuration
public class PaymentClientConfig {
@Bean
public RestClient paymentRestClient(RestClient.Builder builder, ShopProperties shop) {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(shop.payment().timeout());
factory.setReadTimeout(shop.payment().timeout());
return builder
.baseUrl("https://api." + shop.payment().provider() + ".example")
.requestFactory(factory)
.build();
}
}
Pass only the settings a class really needs. A service that only needs the payment settings can receive shop.payment() rather than the whole ShopProperties object.
13. Different Values for Each Environment
Your local machine, test server and production need different values. Put shared values in application.properties and only the differences in profile-specific files:
shop.name=Book Corner
shop.payment.provider=stripe
shop.payment.timeout=10s
shop.payment.timeout=5s
shop.payment.max-retries=5
Start the application with --spring.profiles.active=prod, and the values from application-prod.properties replace the matching shared ones. Your ShopProperties class does not change at all. See Spring Boot Profiles for the full picture.
13.1 Which value wins?
When the same property is set in several places, Spring Boot uses this order. Higher in the list wins:
- Command-line arguments, such as
--shop.payment.timeout=3s - Environment variables, such as
SHOP_PAYMENT_TIMEOUT=3s - Profile-specific files, such as
application-prod.properties application.propertiesorapplication.yml- Defaults in your class, such as
@DefaultValue
This is why secrets are usually passed as environment variables: they override the file without ever being written into it.
14. IDE Auto-Completion with the Configuration Processor
Spring Boot’s own properties come with auto-completion in IntelliJ IDEA, Eclipse and VS Code. You can get the same for your own properties by adding the configuration processor:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
For Gradle:
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
When you build the project, the processor writes a file called META-INF/spring-configuration-metadata.json that describes every property: its name, type, default value and description. Your IDE reads it and then:
- Suggests
shop.payment.max-retriesas you type. - Shows the description from the field’s Javadoc comment.
- Warns you about unknown or misspelled property names.
@ConfigurationProperties(prefix = "shop")
public class ShopProperties {
/**
* Largest number of items a customer can put in a single order.
*/
private int maxItemsPerOrder = 20;
// getter and setter
}
Rebuild the project after changing a properties class, so the metadata file is updated.
15. Binding Properties to Third-Party Classes
Sometimes you want to configure a class you cannot change, such as a connection pool from a library. Put @ConfigurationProperties on a @Bean method, and Spring binds the properties to the object it returns by calling its setters:
package com.example.shop.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ReportingDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "shop.reporting.datasource")
public HikariDataSource reportingDataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}
}
shop.reporting.datasource.jdbc-url=jdbc:postgresql://reports-db:5432/reports
shop.reporting.datasource.username=reporter
shop.reporting.datasource.maximum-pool-size=5
The property names must match the setters of the returned class. HikariDataSource has setJdbcUrl and setMaximumPoolSize, so the properties are jdbc-url and maximum-pool-size.
Return the concrete type, here HikariDataSource, rather than the DataSource interface, so Spring can see the setters it should call.
16. Testing Configuration Properties
16.1 Using a record directly in unit tests
Because a record is a plain object, a unit test can simply create one. No Spring context is needed. This test uses the record from section 11:
@Test
void rejectsOrdersAboveTheLimit() {
ShopProperties shop = new ShopProperties(
"Test Shop", "test@example.com", 2, null);
CheckoutService service = new CheckoutService(shop);
assertThatThrownBy(() -> service.checkLimits(cartWithItems(3)))
.isInstanceOf(IllegalArgumentException.class);
}
16.2 Testing the binding itself
To check that real property values bind and validate correctly, use ApplicationContextRunner. It starts a tiny Spring context in milliseconds:
package com.example.shop.config;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class ShopPropertiesTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withUserConfiguration(TestConfig.class);
@Test
void bindsValuesAndAppliesDefaults() {
runner.withPropertyValues(
"shop.name=Book Corner",
"shop.support-email=help@bookcorner.example",
"shop.payment.provider=stripe")
.run(context -> {
ShopProperties shop = context.getBean(ShopProperties.class);
assertThat(shop.name()).isEqualTo("Book Corner");
assertThat(shop.maxItemsPerOrder()).isEqualTo(20); // default
assertThat(shop.payment().timeout()).isEqualTo(Duration.ofSeconds(10));
});
}
@Test
void failsToStartWithAnInvalidEmail() {
runner.withPropertyValues(
"shop.name=Book Corner",
"shop.support-email=not-an-email",
"shop.payment.provider=stripe")
.run(context -> assertThat(context).hasFailed());
}
@EnableConfigurationProperties(ShopProperties.class)
static class TestConfig {
}
}
For tests that need the full application, use @SpringBootTest(properties = "shop.max-items-per-order=5") to override individual values. See the Spring Boot Testing guide for more.
17. Best Practices
- Group related settings under one clear prefix, such as
shop.payment, and give each group its own class or nested record. - Prefer records (or constructor binding) so configuration cannot change at runtime.
- Use
@ConfigurationPropertiesScanon the main class instead of@Componenton each properties class. - Validate required values and ranges with
@Validated, so bad configuration fails fast at startup. - Use real types:
Duration,DataSize, enums andBigDecimalinstead of strings and plain numbers. - Give sensible defaults so the application works with minimal configuration.
- Write property names in kebab case (
max-retries) in your files. - Never commit secrets. Pass passwords and API keys through environment variables or a secret manager.
- Add the configuration processor and Javadoc comments so your team gets auto-completion and descriptions.
- Keep business logic out of properties classes; they should only hold values.
18. Common Mistakes and Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
All values are null or 0 | The class was never registered as a bean, or the prefix does not match the file | Add @ConfigurationPropertiesScan and check the prefix spelling |
| JavaBean class stays empty | Setters are missing | Add setters, or switch to a record |
A nested record is null | No property under that nested prefix is set | Add an empty @DefaultValue to the nested parameter |
| Startup error about an invalid prefix | The prefix uses camel case, such as myShop | Use lower-case kebab case: my-shop |
| Validation annotations are ignored | @Validated or the validation starter is missing | Add both; add @Valid for nested objects |
@ConstructorBinding does not compile | The Spring Boot 2 import is used | Use org.springframework.boot.context.properties.bind.ConstructorBinding on a constructor, or remove it |
| A timeout is far too short | timeout=10 was read as 10 milliseconds | Write the unit: 10s |
| An environment variable is ignored | Its name does not follow the conversion rules | Use upper case, underscores for dots, no dashes: SHOP_PAYMENT_MAXRETRIES |
| No auto-completion in the IDE | The configuration processor is missing or the project was not rebuilt | Add the processor and rebuild |
19. Summary
@ConfigurationProperties is the recommended way to manage external configuration in Spring Boot. It gathers related settings into one type-safe class, converts values to the right types, supports nested objects, lists and maps, and accepts names in many styles, so environment variables work out of the box. Use records for immutable configuration, add defaults and validation so mistakes are caught at startup, use profiles for environment-specific values, and add the configuration processor for IDE auto-completion. The result is configuration code that is clean, safe, easy to test and ready for production.
Frequently Asked Questions (FAQ)
Q1. When should I use @Value instead of @ConfigurationProperties?
Use @Value for a single, standalone value that does not belong to a group, or when you need a SpEL expression such as #{systemProperties['user.home']}.
As soon as you have two or more related settings, such as a URL, a timeout and a retry count for the same service, switch to @ConfigurationProperties. You get one place for the names, type conversion, validation and IDE auto-completion.
Q2. Why are all the fields in my properties class null?
Check these causes, in this order:
- The class is not registered. Add
@ConfigurationPropertiesScanto the main class, or list it in@EnableConfigurationProperties. - The prefix does not match.
prefix = "shop"only binds properties whose names start withshop. - Setters are missing in a JavaBean-style class.
- You created the object yourself with
newinstead of letting Spring inject it. - The file is not loaded, for example because it is named
application-dev.propertiesbut thedevprofile is not active.
Q3. Do I need getters and setters?
| Style | Setters | Getters |
|---|---|---|
| JavaBean class | Required: Spring calls them to set values | Needed so your code can read values |
| Record | Not needed | Generated automatically, such as name() |
| Class with one constructor | Not needed | You write them, or use Lombok |
For new code, records are the simplest choice: no boilerplate at all.
Q4. Can I use a Java record with @ConfigurationProperties?
Yes. Records are the recommended style since Spring Boot 3. Spring Boot passes the values to the record’s constructor, so the result is immutable:
@ConfigurationProperties(prefix = "shop.mail")
public record MailProperties(String from, String replyTo, @DefaultValue("true") boolean enabled) {
}
Remember to register it with @ConfigurationPropertiesScan or @EnableConfigurationProperties. @Component does not work for records.
Q5. Why does Spring Boot reject my prefix "myShop"?
Prefixes must be written in lower-case kebab case: lower-case letters, digits, dashes and dots. Camel case is not allowed in the prefix, even though it is accepted in property values and field names.
@ConfigurationProperties(prefix = "myShop") // fails at startup
@ConfigurationProperties(prefix = "my-shop") // correct
The files can then use my-shop.name, and an environment variable can use MYSHOP_NAME.
Q6. How do I keep passwords and API keys out of my properties files?
Keep the property in your class, but do not put the real value in a committed file. Instead:
- Pass it as an environment variable, such as
SHOP_PAYMENT_APIKEY, which overrides the file. - Use your platform’s secret store, such as Kubernetes Secrets, HashiCorp Vault or a cloud secret manager.
- For local development, use a file that is listed in
.gitignore, imported withspring.config.import=optional:file:./secrets.properties.
Also avoid logging the whole properties object, because its toString() may print the secret.
Q7. What is the difference between @ConfigurationPropertiesScan and @EnableConfigurationProperties?
@ConfigurationPropertiesScanfinds every@ConfigurationPropertiesclass in a package and its sub-packages automatically. It is the easiest choice for applications.@EnableConfigurationProperties(MyProps.class)registers only the classes you list. It is preferred in libraries and auto-configuration, where you want the properties bean to exist only when that configuration is active.
Q8. Can I change property values without restarting the application?
Not with plain Spring Boot. Values are bound once at startup, and records are immutable by design, which keeps behavior predictable.
If you really need runtime changes:
- Spring Cloud can re-bind
@ConfigurationPropertiesbeans when configuration changes, for example from a Config Server. - For frequently changing values such as feature flags, a dedicated feature-flag service or a database table is often a better fit than configuration files.
Q9. How do I bind a list of objects in YAML?
Use a YAML list where each item has the fields of your record:
shop:
partners:
- name: FastShip
url: https://fastship.example
- name: EcoPost
url: https://ecopost.example
@ConfigurationProperties(prefix = "shop")
public record ShopProperties(List<Partner> partners) {
public record Partner(String name, URI url) {
}
}
In a .properties file, the same list uses indexes: shop.partners[0].name=FastShip.
Q10. Why is my nested record null even though the parent binds correctly?
With constructor binding, a nested object is only created if at least one property under its prefix is set. If none of the shop.payment.* properties exist, payment is null.
Add an empty @DefaultValue to always create it, using its own defaults:
public record ShopProperties(String name, @DefaultValue Payment payment) {
public record Payment(@DefaultValue("3") int maxRetries) {
}
}
Q11. Are placeholders like ${...} allowed in property values?
Yes. Property values can refer to other properties or environment variables, with an optional default after a colon:
shop.base-url=https://bookcorner.example
shop.orders-url=${shop.base-url}/orders
shop.payment.api-key=${PAYMENT_API_KEY:dev-key}
SpEL expressions such as #{...}, however, are not evaluated in @ConfigurationProperties. Use @Value if you really need them.
Q12. How can I see which values were actually bound at runtime?
Use Spring Boot Actuator’s configprops endpoint. It lists every @ConfigurationProperties bean, its prefix and its current values, along with where each value came from.
curl http://localhost:8080/actuator/configprops/shop
Values are shown as ****** by default to protect secrets. Set management.endpoint.configprops.show-values=when-authorized and secure the endpoint to see them.
Q13. Can I put helper methods in a properties class?
Small, read-only helpers that only use the class’s own values are fine and can make calling code clearer:
public record ShippingProperties(BigDecimal freeOver, BigDecimal standardFee) {
public BigDecimal feeFor(BigDecimal orderTotal) {
return orderTotal.compareTo(freeOver) >= 0 ? BigDecimal.ZERO : standardFee;
}
}
Do not inject other beans or put real business logic there. A properties class should stay a simple holder of settings.
Q14. Should I use Optional for properties that may be missing?
No. Spring Boot does not recommend Optional fields in properties classes; Optional is meant for method return values.
Better options:
- Give the property a default with
@DefaultValue. - Allow
nulland check it where it is used. - Add an accessor that returns
Optional, such asOptional.ofNullable(apiKey), if callers find that clearer.
Q15. How are enum values matched?
Leniently. Spring Boot ignores case and dashes or underscores when converting text to an enum, so all of these bind to PaymentProvider.BANK_TRANSFER:
shop.payment.provider=BANK_TRANSFER
shop.payment.provider=bank_transfer
shop.payment.provider=bank-transfer
An unknown value, such as a typo, stops the application at startup with a message listing the problem.
Q16. Do lists and maps merge when set in several files?
They behave differently:
- Lists are replaced. If
application-prod.propertiessetsshop.currencies, the whole list fromapplication.propertiesis ignored. - Maps are merged. Keys from both files are combined, and a key set in the higher-priority file overrides the same key from the other.
So if a profile needs one extra currency, repeat the full list in the profile file.
Q17. How do I document my properties for other developers?
- Add the configuration processor; it generates metadata so IDEs show auto-completion.
- Write a Javadoc comment on each field; it becomes the description shown in the IDE.
- For extra hints, such as a list of allowed values, add
META-INF/additional-spring-configuration-metadata.json. - Keep a short, commented example configuration in your project’s README.
Q18. Can two properties classes share the same prefix?
Technically yes, but avoid it. Both classes would bind the same properties independently, which is confusing and makes it unclear who owns each setting.
Instead, give each class its own sub-prefix, such as shop.payment and shop.shipping, or nest them as records inside one ShopProperties class.
Q19. How do I use a different configuration file name or location?
Your @ConfigurationProperties class does not care where values come from. To load extra files, use spring.config.import:
# Load an extra file; "optional:" means no error if it is missing
spring.config.import=optional:classpath:shop-defaults.properties,optional:file:/etc/shop/shop.properties
You can also point to a completely different location at startup with --spring.config.location=..., but spring.config.import is usually simpler and safer.
Q20. Is binding slow, or does it affect performance?
No. Binding happens once at startup and takes a tiny fraction of the start time. After that, reading a value is just a normal method call on a Java object, as fast as reading any field.
This is another advantage over looking values up repeatedly with Environment.getProperty() inside frequently called code.