Spring Properties, @Value, @ConfigurationProperties and Profiles
🎧 Focus sounds
Soft beat sounds that many learners like while reading. Pick one, press Play, and keep studying.
Keep the volume low. These sounds are for comfort while studying, not a medical treatment. If you have epilepsy or are sensitive to pulsing sounds, use the background noise only.
In this lesson
Database URLs, API keys, feature flags and limits change between your laptop, testing and production. Hard-coding them means rebuilding the app for every environment. Spring reads them from properties, so the same build runs everywhere.
application.properties#
# application.properties
app.name=VitalStack
app.max-cart-items=20
app.support-email=help@example.com
app.payment.provider=razorpay
app.payment.timeout=5s
app.payment.retries=2
Or the same in YAML (application.yml):
app:
name: VitalStack
max-cart-items: 20
payment:
provider: razorpay
timeout: 5s
retries: 2
@Value: one value at a time#
@Service
public class CartService {
private final int maxItems;
public CartService(@Value("${app.max-cart-items:10}") int maxItems) { // ":10" = default
this.maxItems = maxItems;
}
}
Fine for one or two values. For groups of settings, use typed properties.
@ConfigurationProperties: typed, validated groups#
package com.vitalstack;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(prefix = "app.payment")
public record PaymentProperties(String provider, Duration timeout, int retries) { }
package com.vitalstack;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
@SpringBootApplication
@EnableConfigurationProperties(PaymentProperties.class)
public class Main {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Main.class);
app.setWebApplicationType(org.springframework.boot.WebApplicationType.NONE);
app.run(args).close();
}
@Bean
CommandLineRunner show(PaymentProperties pay, Environment env) {
return args -> {
System.out.println("provider=" + pay.provider() + ", timeout=" + pay.timeout().toMillis()
+ "ms, retries=" + pay.retries());
System.out.println("app.name=" + env.getProperty("app.name"));
System.out.println("profiles=" + String.join(",", env.getActiveProfiles()));
};
}
}
# application.properties
spring.main.banner-mode=off
logging.level.root=warn
app.name=VitalStack
app.payment.provider=razorpay
app.payment.timeout=5s
app.payment.retries=2
Output:
provider=razorpay, timeout=5000ms, retries=2
app.name=VitalStack
profiles=
Benefits over @Value: type conversion (5s → Duration), one place for related settings, IDE autocompletion, and validation with @Validated + @NotBlank, @Min.
Profiles: different settings per environment#
Create files per profile:
application.properties ← shared defaults
application-dev.properties ← used when profile "dev" is active
application-prod.properties ← used when profile "prod" is active
# application-dev.properties
spring.datasource.url=jdbc:h2:mem:devdb
app.payment.provider=mock
logging.level.com.vitalstack=debug
Activate a profile:
java -jar app.jar --spring.profiles.active=prod
# or an environment variable
SPRING_PROFILES_ACTIVE=prod java -jar app.jar
Profile-specific values override the defaults. Beans can be profile-specific too:
@Bean
@Profile("dev")
CommandLineRunner seedDemoData(CourseRepository repo) { ... }
Where values can come from (highest priority first)#
- Command-line arguments (
--app.payment.retries=5) - Environment variables (
APP_PAYMENT_RETRIES=5: dots become underscores, upper case) - Profile-specific files (
application-prod.properties) application.properties/application.yml- Defaults in code (
@Value("${x:default}"))
So the same jar can be configured by your hosting platform or Docker through environment variables, without editing files.
Never commit secrets (database passwords, API keys, JWT secrets) to Git. Reference them as ${DB_PASSWORD} in properties and supply them through environment variables or a secrets manager. If a secret ever lands in Git, rotate it: deleting the commit isn’t enough.
spring.datasource.password=${DB_PASSWORD}
app.jwt.secret=${JWT_SECRET}
Exercise#
- Create
@ConfigurationProperties(prefix = "app.mail")withfrom,hostandport, and print them at startup. - Add
application-dev.propertiesandapplication-prod.propertieswith differentapp.namevalues and switch between them with--spring.profiles.active. - Override a property with an environment variable and confirm it wins over the file.
- Add
@Validatedand@Min(1)toretries, set it to 0, and read the startup error.
Hint for 3
APP_NAME=FromEnv java -jar app.jar overrides app.name thanks to relaxed binding.
Interview corner#
- @Value vs @ConfigurationProperties? @Value injects single values; @ConfigurationProperties binds a typed, validated group with relaxed binding.
- What are profiles? Named sets of configuration and beans activated per environment (dev, test, prod).
- Property precedence? Command line > environment variables > profile files > application.properties > code defaults (simplified).
- How do you handle secrets? Environment variables or a vault/secret manager, never in source control.
Summary#
- Externalise settings in
application.properties/.yml. @Valuefor single values;@ConfigurationPropertiesrecords for typed groups.- Profiles switch config and beans per environment; environment variables override files.
- Keep secrets out of Git.
Next: AOP, adding behaviour around methods.
Quick quiz
3 questions. Check what stuck and earn 2 XP for each right answer.
Progress is saved in this browser. Create a free account to keep it on every device.
Get new lessons in your inbox
One email when we publish a new tutorial or guide. No spam, unsubscribe in one click.