
application.properties vs application.yml in Spring Boot: Making the Right Choice
Explore the differences between application.properties and application.yml in Spring Boot. Learn when to use each format, how to implement type-safe configuration with Kotlin, and follow best practices for organizing your application settings.
Spring Boot offers two popular ways to configure your application: properties files and YAML files. In this guide, we'll explore both approaches, their advantages, and help you choose the right format for your project.
Key Takeaways
- Understanding the differences between .properties and .yml formats
- When to choose each format
- How to use configuration properties with Kotlin data classes
- Best practices for organizing your configuration
Basic Comparison
Let's start with a simple comparison using the same configuration in both formats:
# application.properties
server.port=8080
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=user
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
app.api.version=v1
app.api.security.enabled=true
app.api.security.token-expiration=3600
# application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: secret
jpa:
hibernate:
ddl-auto: update
app:
api:
version: v1
security:
enabled: true
token-expiration: 3600
Type-Safe Configuration with Kotlin
Spring Boot works beautifully with Kotlin data classes for type-safe configuration:
@ConfigurationProperties(prefix = "app.api")
@ConstructorBinding
data class ApiConfig(
val version: String,
val security: SecurityConfig
) {
data class SecurityConfig(
val enabled: Boolean,
val tokenExpiration: Long
)
}
// Enable configuration properties scanning
@SpringBootApplication
@ConfigurationPropertiesScan
class MyApplication
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
// Usage in a service
@Service
class ApiService(private val apiConfig: ApiConfig) {
fun getApiVersion() = apiConfig.version
fun isSecurityEnabled() = apiConfig.security.enabled
}
Environment-Specific Configuration
Both formats support profile-specific configuration:
# application-dev.properties
app.api.security.enabled=false
logging.level.root=DEBUG
# application-prod.properties
app.api.security.enabled=true
logging.level.root=INFO
# application.yml
spring:
profiles:
active: dev
---
spring:
config:
activate:
on-profile: dev
app:
api:
security:
enabled: false
logging:
level:
root: DEBUG
---
spring:
config:
activate:
on-profile: prod
app:
api:
security:
enabled: true
logging:
level:
root: INFO
Lists and Arrays
YAML shines when dealing with lists and arrays:
# application.properties
app.cors.allowed-origins[0]=https://example.com
app.cors.allowed-origins[1]=https://dev.example.com
app.cors.allowed-methods=GET,POST,PUT,DELETE
# application.yml
app:
cors:
allowed-origins:
- https://example.com
- https://dev.example.com
allowed-methods:
- GET
- POST
- PUT
- DELETE
Here's how to handle this in Kotlin:
@ConfigurationProperties(prefix = "app.cors")
data class CorsConfig(
val allowedOrigins: List<String>,
val allowedMethods: List<String>
)
@Configuration
class WebConfig(private val corsConfig: CorsConfig) : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins(*corsConfig.allowedOrigins.toTypedArray())
.allowedMethods(*corsConfig.allowedMethods.toTypedArray())
}
}
Complex Objects
YAML's hierarchical structure makes it easier to read complex configurations:
# application.yml
app:
cache:
providers:
redis:
host: localhost
port: 6379
database: 0
memory:
maximum-size: 1000
eviction-policy: LRU
scheduling:
tasks:
cleanup:
cron: "0 0 * * * *"
enabled: true
report-generation:
cron: "0 0 0 * * *"
enabled: false
The corresponding Kotlin classes:
@ConfigurationProperties(prefix = "app")
data class AppConfig(
val cache: CacheConfig,
val scheduling: SchedulingConfig
) {
data class CacheConfig(
val providers: Providers
) {
data class Providers(
val redis: RedisConfig,
val memory: MemoryConfig
)
data class RedisConfig(
val host: String,
val port: Int,
val database: Int
)
data class MemoryConfig(
val maximumSize: Int,
val evictionPolicy: String
)
}
data class SchedulingConfig(
val tasks: Map<String, TaskConfig>
) {
data class TaskConfig(
val cron: String,
val enabled: Boolean
)
}
}
When to Choose Each Format
Choose Properties (.properties) when:
- You need maximum compatibility
- Your configurations are simple and flat
- You're working with legacy systems
- You want to use system property overrides easily
Choose YAML (.yml) when:
- You have hierarchical configuration data
- You need to maintain multiple profiles in one file
- You work with lists and arrays frequently
- You want better readability for complex configurations
Best Practices
- Be Consistent
- Choose one format and stick to it
- Don't mix .properties and .yml files unless necessary
- Use Profile-Specific Files
- Keep base configuration in
application.yml
- Override in
application-{profile}.yml
- Keep base configuration in
Document Your Configuration
@ConfigurationProperties(prefix = "app")
data class AppConfig(
/** Application name used in logs and metrics */
val name: String,
/** Current API version number */
val version: Int
)
Use Type-Safe Configuration
@ConfigurationProperties(prefix = "app")
@Validated
data class AppConfig(
@field:NotBlank
val name: String,
@field:Min(1)
val version: Int,
val features: Features
) {
data class Features(
val enabled: Boolean = false,
val maxItems: Int = 10
)
}
Common Pitfalls
- YAML Indentation
- YAML is sensitive to indentation
- Use spaces, not tabs
- Maintain consistent indentation
- Property Name Case
- Use kebab-case in files:
token-expiration
- Use camelCase in Kotlin:
tokenExpiration
- Don't forget to activate profiles:
- Use kebab-case in files:
Profile Activation
@Profile("dev")
@Configuration
class DevConfig
Next Steps
- Learn about externalized configuration
- Explore Spring Cloud Config for distributed systems
- Study Spring Boot's relaxed binding rules
- Implement configuration validation
Remember: Both formats are equally powerful, and Spring Boot handles them seamlessly. Choose the one that best fits your team's needs and coding style.