Configuration & Properties
Reviewed & published by Brayan K
Externalize your settings. application.properties / application.yml hold configuration outside the code, @Value injects single values, and @ConfigurationProperties binds whole groups to typed objects.
Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
Before You Start
You should understand Spring beans and dependency injection, since configuration values are injected into beans. No prior YAML knowledge is required.
What You'll Learn
- Why externalize configuration
- Writing application.properties and application.yml
- Injecting single values with @Value
- Providing defaults with ${key:default}
- Binding groups with @ConfigurationProperties
- Overriding values per environment
The Big Idea — Dials on the Outside of the Box
💡 Analogy: Hardcoding settings is like soldering the volume at one level inside a sealed radio — to change it you must open and rebuild the device. Externalized configuration puts the dials on the outside: the same radio (your one build) runs anywhere, and you just turn the knobs (application.properties, environment variables) for each room. @Value reads one dial; @ConfigurationProperties reads a whole labeled panel of related dials at once.
Build once, configure per environment — never recompile to change a value.
1️⃣ The Configuration Files
Spring Boot auto-loads application.properties (flat key=value) or application.yml (nested). They are equivalent — a nested YAML key maps to a dotted property key.
# application.properties (key=value pairs)
server.port=8080
app.name=Bookstore
app.timeout=30
app.support-email=help@bookstore.test
app.feature-flags=search,recommendations,reviews
app.mail.host=smtp.bookstore.test
app.mail.port=587# application.yml (the same configuration, nested)
server:
port: 8080
app:
name: Bookstore
timeout: 30
support-email: help@bookstore.test
feature-flags:
- search
- recommendations
- reviews
mail:
host: smtp.bookstore.test
port: 5872️⃣ Injecting Single Values — @Value
@Value("${key}") injects one property. Add a default after a colon — ${app.timeout:30} — so a missing key falls back gracefully.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
// @Value injects ONE property at a time. Good for a handful of simple values.
@Component
public class AppInfo {
@Value("${app.name}")
private String name; // from app.name
// ":30" is a DEFAULT used if app.timeout is absent.
@Value("${app.timeout:30}")
private int timeoutSeconds;
@Value("${app.support-email}")
private String supportEmail;
public String describe() {
return name + " (timeout=" + timeoutSeconds + "s, support=" + supportEmail + ")";
}
}3️⃣ Binding Groups — @ConfigurationProperties
For many related settings, @ConfigurationProperties(prefix = "app") binds the whole group — including nested objects and lists — to a typed class. Relaxed binding maps app.support-email to a supportEmail field.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
// @ConfigurationProperties binds a WHOLE GROUP under a prefix to a typed object.
// Far cleaner than many @Value fields when settings are related.
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private int timeout = 30; // field default
private String supportEmail; // binds app.support-email (relaxed)
private List<String> featureFlags; // binds the list / CSV
private Mail mail = new Mail(); // nested group app.mail.*
public static class Mail {
private String host;
private int port;
// getters/setters...
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
}
// getters/setters for each field (required for binding)...
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
public List<String> getFeatureFlags() { return featureFlags; }
public void setFeatureFlags(List<String> f) { this.featureFlags = f; }
public Mail getMail() { return mail; }
}import java.util.*;
// PLAIN-JAVA illustration of how key=value config is READ, with a DEFAULT
// fallback (like @Value("${app.timeout:30}")).
public class Main {
// pretend these were loaded from application.properties
static Map<String, String> props = Map.of(
"app.name", "Bookstore",
"app.support-email", "help@bookstore.test",
"app.feature-flags", "search,recommendations,reviews");
// read with a default if the key is missing
static String get(String key, String def) {
return props.getOrDefault(key, def);
}
public static void main(String[] args) {
String name = get("app.name", "Unnamed");
int timeout = Integer.parseInt(get("app.timeout", "30")); // missing -> default 30
List<String> flags = Arrays.asList(get("app.feature-flags", "").split(","));
System.out.println("name : " + name);
System.out.println("timeout : " + timeout);
System.out.println("flags : " + flags);
}
}🧠 Quick Recall
Predict before you peek.
Answer: a default used only when app.timeout is missing.
Answer: app.support-email (relaxed binding maps kebab-case to camelCase).
Answer: so one build runs in every environment by changing values, not recompiling code.
🎯 YOUR TURN — Inject payment settings
Use @Value to inject an API URL, a currency (with a default), and a retry count.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
// YOUR TURN: inject a few properties with @Value.
// Given in application.properties:
// payment.api-url=https://pay.example.test
// payment.currency=USD
// payment.retries=3
// - inject api-url into a String
// - inject currency with a DEFAULT of "USD" if missing
// - inject retries into an int
//
// @Component
// public class PaymentConfig {
// @Value("${payment.api-url}") private String apiUrl;
// ...
// }🧩 MINI-CHALLENGE — Bind a storage group
Bind a storage.* group — including a list — with @ConfigurationProperties.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
// MINI-CHALLENGE: bind a whole group with @ConfigurationProperties.
// Given:
// storage.bucket=uploads
// storage.region=eu-west-1
// storage.allowed-types=png,jpg,pdf
// storage.max-size-mb=25
// - prefix = "storage"
// - fields: bucket, region, allowedTypes (List<String>), maxSizeMb (int)
// - remember: relaxed binding maps allowed-types -> allowedTypes
//
// Think: when do you prefer @ConfigurationProperties over @Value?Common Errors
- ❌ Hardcoding settings in Java. Needs a rebuild to change. Fix: externalize to a properties/YAML file.
- ❌ Missing @Value default. A missing key fails startup. Fix: add ${key:default}.
- ❌ No getters/setters for @ConfigurationProperties. Binding fails silently. Fix: add accessors (or use a record/constructor binding).
- ❌ YAML indentation mistakes. Wrong structure or parse error. Fix: use consistent spaces, never tabs.
- ❌ Committing secrets to the file. Leaks credentials. Fix: use environment variables / a secrets manager.
- ❌ Scattering many @Value fields. Hard to maintain. Fix: group with @ConfigurationProperties.
Pro Tips
- 💡 Prefer @ConfigurationProperties for any group of related settings — it is type-safe and validatable.
- 💡 Keep secrets out of files — inject them via environment variables at runtime.
- 💡 Override per environment with command-line args or env vars rather than editing the build.
- 💡 Always supply defaults for optional values so missing keys don't break startup.
📋 Quick Reference
| Element | Purpose | Note |
|---|---|---|
| application.properties | flat key=value | Auto-loaded |
| application.yml | nested config | Same keys |
| @Value("${k}") | inject one value | Single property |
| ${k:default} | fallback value | If key missing |
| @ConfigurationProperties | bind a group | Typed object |
| prefix = "app" | group namespace | app.* keys |
| relaxed binding | kebab -> camel | support-email |
| List binding | CSV / YAML list | to List/array |
| override | env var / CLI arg | Per environment |
📚 Resources / Keep Learning
- 📘 Spring Boot — externalized configuration (docs.spring.io)
- 📗 Type-safe @ConfigurationProperties (docs.spring.io)
- 📗 Using @Value (docs.spring.io)
🎉 Lesson Complete!
You can now externalize settings in application.properties or application.yml, inject single values with @Value and defaults, bind whole groups with @ConfigurationProperties, and override values per environment without recompiling.
Practice quiz
What is the main purpose of application.properties (or application.yml)?
- To externalize configuration outside the code
- To compile the app
- To define database tables
- To replace controllers
Answer: To externalize configuration outside the code. These files externalize configuration so settings can change without recompiling the application.
What does @Value("${server.port}") do?
- Defines a bean
- Injects the value of the server.port property
- Starts a server
- Creates a property file
Answer: Injects the value of the server.port property. @Value injects a single property value, resolving the ${...} placeholder from the configuration.
In @Value("${app.timeout:30}"), what is the 30?
- A bean id
- A port number
- A default used when app.timeout is missing
- A timeout in hours required
Answer: A default used when app.timeout is missing. The text after the colon is the default value used if the property is not defined.
What does @ConfigurationProperties(prefix = "app") bind?
- A single value only
- A REST endpoint
- A SQL query
- A group of properties under the app prefix to a typed object
Answer: A group of properties under the app prefix to a typed object. @ConfigurationProperties binds a whole group of related properties (under a prefix) to fields of a class.
Which approach is better for many related settings?
- Many separate @Value fields
- @ConfigurationProperties on a typed class
- Hardcoding in Java
- Environment variables only
Answer: @ConfigurationProperties on a typed class. @ConfigurationProperties groups related settings into one type-safe, validated object, which scales better than many @Value fields.
How is a nested YAML property app.mail.host referenced as a flat key?
- app.mail.host
- app-mail-host
- app/mail/host
- appMailHost
Answer: app.mail.host. YAML nesting maps to dotted keys, so app: mail: host becomes app.mail.host.
Why externalize configuration instead of hardcoding it?
- It is required by Java
- It makes code longer
- So the same build runs with different settings per environment
- It disables logging
Answer: So the same build runs with different settings per environment. Externalized config lets one immutable build run anywhere by changing values, not code.
Which is a valid way to override a property at runtime?
- Editing the JAR bytes
- Recompiling for each value
- It cannot be overridden
- A command-line argument or environment variable
Answer: A command-line argument or environment variable. Spring Boot lets command-line args and environment variables override file-based properties.
What type can @ConfigurationProperties bind a comma-separated list to?
- Only String
- A List or array field
- Only int
- Nothing
Answer: A List or array field. Spring Boot can relax-bind comma-separated values (or YAML lists) to a List or array field.
What does the missing-key default in @Value("${k:fallback}") prevent?
- Slow startup
- A startup failure when the property is absent
- Extra logging
- A compile error
Answer: A startup failure when the property is absent. Providing a default after the colon lets the bean start even when the property is not defined.
Continue this course
- Previous: DTOs & Mapping
- Next: Profiles & Environments — @Profile and per-environment configuration
- Quick reference: Java cheat sheet
Frequently asked questions
What is the point of application.properties or application.yml?
They externalize configuration — the settings your application needs to run, such as the server port, database URLs, timeouts, feature flags, and credentials references — out of compiled code and into a file Spring Boot reads at startup. This means one immutable build can run in many situations just by changing the file or overriding values, rather than recompiling. Spring Boot loads these files automatically from the classpath or working directory and makes every key available to the rest of the app.
What is the difference between properties and YAML format?
They express the same configuration in different syntaxes. application.properties uses flat key=value lines, like app.mail.host=smtp.test. application.yml uses indentation to nest the same keys hierarchically under app: then mail: then host:. YAML is more compact for deeply nested or list-valued configuration and is often preferred for readability, while properties files are simpler and avoid YAML's indentation sensitivity. Spring Boot supports both, and a nested YAML key maps to the equivalent dotted property key.
When should I use @Value versus @ConfigurationProperties?
@Value injects a single property where it is needed, optionally with a default after a colon, which is convenient for one or two isolated values. @ConfigurationProperties binds a whole group of related properties under a common prefix to the fields of a typed class, including nested objects and lists. For more than a few related settings, @ConfigurationProperties is cleaner, type-safe, supports relaxed binding and validation, and keeps configuration in one well-named object instead of scattering @Value strings throughout the codebase.
What is relaxed binding in @ConfigurationProperties?
Relaxed binding lets Spring Boot match property names to Java field names across differing conventions. A property written as app.support-email (kebab-case) binds to a field named supportEmail (camelCase), and variants like APP_SUPPORT_EMAIL from environment variables also map to the same field. This flexibility means you can use the idiomatic style for each source — kebab-case in YAML, upper-snake-case for environment variables — and they all bind to the same camelCase property correctly.
How can I override a configured value without changing the file?
Spring Boot resolves configuration from an ordered list of sources, and later ones override earlier ones. Common overrides include command-line arguments (for example --server.port=9090), OS environment variables (SERVER_PORT=9090), and JVM system properties. This ordering is what makes the same build adapt to different environments: you bake sensible defaults into application.properties and override only what differs per deployment, often via environment variables in containers, without touching the packaged file.