Why this matters
- Hardcoding database URLs or API keys in Java forces recompilation for every environment change.
- Profiles activate bean sets and property files per environment —
devuses H2,produses PostgreSQL. @ConfigurationPropertiesbinds typed configuration objects, replacing scattered@Valueinjections.
application.yml structure
spring:
application:
name: bookstore-api
datasource:
url: jdbc:postgresql://127.0.0.1:5432/bookstore
username: bookstore
password: ${DB_PASSWORD:localdev}
server:
port: 8080
bookstore:
pricing:
service-url: https://pricing.internal.example.com
timeout-ms: 3000
catalog:
page-size: 20
Spring binds spring.* to built-in properties and custom bookstore.* keys to your @ConfigurationProperties class.
Typed configuration properties
@ConfigurationProperties(prefix = "bookstore.pricing")
public record PricingProperties(
String serviceUrl,
int timeoutMs
) {}
@Configuration
@EnableConfigurationProperties(PricingProperties.class)
public class BookStoreConfig { }
Injection into services:
@Service
public class PricingService {
private final RestClient client;
public PricingService(PricingProperties props) {
this.client = RestClient.builder()
.baseUrl(props.serviceUrl())
.build();
}
}
Records make immutable config objects with validation support via @Validated and @NotBlank.
Profiles
Activate environment-specific configuration:
# application-dev.yml
spring:
datasource:
url: jdbc:h2:mem:bookstore
h2:
console:
enabled: true
logging:
level:
com.example.bookstore: DEBUG
# application-prod.yml
spring:
datasource:
url: jdbc:postgresql://${DB_HOST}:5432/bookstore
Activate with:
java -jar bookstore-api.jar --spring.profiles.active=prod
Or in application.yml:
spring:
profiles:
active: dev
Profile strategies
application-{profile}.yml— Property overrides per profile.@Profile("dev")on beans — Register beans only in matching environments.spring.profiles.active— Comma-separated list; later profiles override earlier ones.
Profile-specific beans
@Configuration
@Profile("dev")
public class DevDataConfig {
@Bean
public CommandLineRunner seedBooks(BookRepository repo) {
return args -> {
repo.save(new Book("The Pragmatic Programmer", "978-0201616224", 42.99));
repo.save(new Book("Clean Code", "978-0132350884", 38.50));
};
}
}
The seeder runs only in dev — production starts with an empty catalog controlled by migrations.
@Value for simple cases
@Service
public class FeatureFlagService {
@Value("${bookstore.features.recommendations-enabled:false}")
private boolean recommendationsEnabled;
}
Default value after colon (:false) applies when the property is absent. Prefer @ConfigurationProperties when you have more than two related keys.
Property precedence
Spring resolves properties from multiple sources (highest priority first):
Property source precedence
- Command-line arguments (
--server.port=9090) SPRING_APPLICATION_JSONenvironment variable- OS environment variables
application-{profile}.ymlapplication.yml
Knowing precedence explains why a command-line override beats a YAML default.
Relaxed binding
These all bind to the same property:
bookstore:
pricing:
service-url: https://example.com
props.getServiceUrl() // Java camelCase
Environment variable: BOOKSTORE_PRICING_SERVICE_URL.
Validation on startup
@ConfigurationProperties(prefix = "bookstore.pricing")
@Validated
public record PricingProperties(
@NotBlank String serviceUrl,
@Positive int timeoutMs
) {}
Invalid config fails at startup with a clear BindValidationException — better than a runtime NPE in production.
Quick recall
Everything you need if you only revisit this box.
application.ymlexternalises all environment-specific BookStore settings.@ConfigurationPropertiesbinds typed, validated config objects from custom prefixes.- Profiles (
dev,prod) switch property files and conditional beans. - Environment variables override YAML — use them for secrets in production.
- Relaxed binding maps
service-url,serviceUrl, andSERVICE_URLto the same field. @Profileon beans registers environment-specific components like dev data seeders.
Test yourself
Answer these before moving on — recall is what makes it stick.