Master Spring Boot configuration: Learn the differences between application.properties and application.yml, best practices, profiles, and advanced techniques for managing environment-specific settings.
JOptimize Team
You've just deployed your Spring Boot application to production, and suddenly it crashes because the database URL is wrong.
Or worse: your logging level is set to DEBUG, and now your logs are filling up disk space.
These aren't code bugs. They're configuration problems. And they happen because most developers don't fully understand how Spring Boot loads and manages configuration.
In this guide, you'll learn exactly how to configure Spring Boot the right way—so your application works in development, staging, AND production without code changes.
application.properties is a file in your src/main/resources folder that tells Spring Boot how to behave.
Instead of hardcoding values like database URLs, ports, or API keys in your code, you put them in this file:
# application.properties server.port=8080 spring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=root spring.datasource.password=secret123 spring.jpa.hibernate.ddl-auto=update
Then in your code, you access these values:
@Component public class MyComponent { @Value("${server.port}") private int port; @Value("${spring.datasource.url}") private String dbUrl; public void init() { System.out.println("Server running on port: " + port); System.out.println("Database: " + dbUrl); } }
Why is this important?
Both do the same thing, but with different syntax.
spring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=root spring.datasource.password=secret123 spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true
spring: datasource: url: jdbc:mysql://localhost:3306/mydb username: root password: secret123 jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: format_sql: true
Which is better?
| Aspect | application.properties | application.yml |
|---|---|---|
| Readability | Flat, repetitive | Hierarchical, cleaner |
| Nesting | Lots of dots | Clear hierarchy |
| Maintenance | Hard to scan | Easy to scan |
| Learning curve | Simpler | Steeper |
| Industry standard | Older projects | Modern projects |
Recommendation: Use application.yml for new projects. It's more readable and less error-prone.
The real magic happens when you use application profiles. This lets you have different configurations for different environments.
src/main/resources/ ├── application.yml (default, shared settings) ├── application-dev.yml (development) ├── application-staging.yml (staging) └── application-prod.yml (production)
# application.yml spring: application: name: my-app jpa: show-sql: false properties: hibernate: format_sql: true server: servlet: context-path: /api
# application-dev.yml spring: datasource: url: jdbc:mysql://localhost:3306/mydb_dev username: root password: dev_password jpa: hibernate: ddl-auto: create-drop # Recreate DB on startup show-sql: true h2: console: enabled: true logging: level: root: INFO com.myapp: DEBUG server: port: 8080
# application-prod.yml spring: datasource: url: jdbc:mysql://prod-db-server:3306/mydb_prod username: ${DB_USERNAME} # From environment variable password: ${DB_PASSWORD} # From environment variable hikari: maximum-pool-size: 20 jpa: hibernate: ddl-auto: validate # NEVER recreate in production show-sql: false logging: level: root: WARN com.myapp: INFO server: port: 8080 ssl: enabled: true key-store: /etc/ssl/keystore.jks key-store-password: ${KEYSTORE_PASSWORD}
Option A: application.yml (recommended)
# application.yml spring: profiles: active: dev # or staging, prod
Option B: System property
java -Dspring.profiles.active=prod -jar app.jar
Option C: Environment variable
export SPRING_PROFILES_ACTIVE=prod java -jar app.jar
Option D: application.properties
# application.properties spring.profiles.active=dev
Hardcoding database passwords in files is a security nightmare. Use environment variables instead.
spring: datasource: url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:mydb} username: ${DB_USERNAME} password: ${DB_PASSWORD}
The syntax ${VARIABLE_NAME:default_value} means:
VARIABLE_NAMEdefault_valueDocker:
FROM openjdk:17 COPY target/app.jar app.jar ENV DB_HOST=db-server ENV DB_USERNAME=prod_user ENV DB_PASSWORD=secret123 ENTRYPOINT ["java", "-jar", "app.jar"]
Kubernetes:
apiVersion: v1 kind: ConfigMap metadata: name: app-config data: DB_HOST: "postgres.default.svc.cluster.local" DB_PORT: "5432" --- apiVersion: v1 kind: Secret metadata: name: app-secrets type: Opaque data: DB_USERNAME: cHJvZF91c2Vy # base64 encoded DB_PASSWORD: c2VjcmV0MTIz # base64 encoded --- apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: template: spec: containers: - name: app image: my-app:1.0 envFrom: - configMapRef: name: app-config - secretRef: name: app-secrets
// WRONG ❌ private static final String DB_PASSWORD = "admin123"; // RIGHT ✅ @Value("${spring.datasource.password}") private String dbPassword;
# WRONG ❌ - Hard to read and maintain spring.datasource.hikari.connection-timeout=20000 spring.datasource.hikari.maximum-pool-size=5 spring.datasource.hikari.idle-timeout=300000 spring.datasource.hikari.max-lifetime=1200000 # RIGHT ✅ - Use application.yml spring: datasource: hikari: connection-timeout: 20000 maximum-pool-size: 5 idle-timeout: 300000 max-lifetime: 1200000
# application.yml (shared) spring: jpa: show-sql: false # application-dev.yml (dev override) spring: jpa: show-sql: true # ✅ Correctly overrides shared setting
# application-dev.yml spring: datasource: hikari: maximum-pool-size: 5 # Small for development # application-prod.yml spring: datasource: hikari: maximum-pool-size: 20 # Large for production
Without this, production uses development settings = database connection exhaustion.
# WRONG ❌ spring: datasource: url: jdbc:mysql://localhost:3306/mydb username: admin # Logged in application logs password: secret123 # EXPOSED in logs! logging: level: org.hibernate.SQL: DEBUG # Logs all SQL = passwords visible # RIGHT ✅ spring: datasource: url: jdbc:mysql://localhost:3306/mydb username: ${DB_USERNAME} # Not logged password: ${DB_PASSWORD} # Not logged logging: level: org.hibernate.SQL: INFO # Only show errors
Instead of scattering @Value annotations everywhere, use @ConfigurationProperties for better organization:
@Configuration @ConfigurationProperties(prefix = "app") public class AppConfig { private String name; private Database database; private Cache cache; public static class Database { private String url; private int poolSize; private int timeout; // getters/setters } public static class Cache { private int ttl; private int maxSize; // getters/setters } // getters/setters }
Then in application.yml:
app: name: MyApp database: url: jdbc:mysql://localhost:3306/mydb poolSize: 10 timeout: 5000 cache: ttl: 3600 maxSize: 1000
Access in your code:
@Service public class MyService { private final AppConfig config; public MyService(AppConfig config) { this.config = config; } public void init() { System.out.println("Pool size: " + config.getDatabase().getPoolSize()); } }
server: port: 8080 servlet: context-path: /api ssl: enabled: true key-store: classpath:keystore.jks key-store-password: secret
spring: datasource: url: jdbc:mysql://localhost:3306/mydb username: root password: secret driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # create | create-drop | update | validate | none show-sql: false properties: hibernate: format_sql: true dialect: org.hibernate.dialect.MySQL8Dialect
spring: datasource: url: jdbc:postgresql://localhost:5432/mydb username: postgres password: secret driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update properties: hibernate: dialect: org.hibernate.dialect.PostgreSQL10Dialect
logging: level: root: INFO com.myapp: DEBUG org.springframework.web: WARN org.hibernate: WARN pattern: console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n" file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" file: name: logs/application.log
spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 20000 # 20 seconds idle-timeout: 300000 # 5 minutes max-lifetime: 1200000 # 20 minutes
Use @ConfigurationProperties with @Validated:
@Configuration @ConfigurationProperties(prefix = "app") @Validated public class AppConfig { @NotBlank(message = "App name cannot be blank") private String name; @Min(1) @Max(65535) private int port; @Email private String adminEmail; // getters/setters }
Spring Boot will fail fast on startup if configuration is invalid:
*************************** APPLICATION FAILED TO START *************************** Field adminEmail in com.myapp.config.AppConfig is invalid: Value 'admin@invalid' is not a valid email address
This single change can improve database performance 10-50x:
# application-dev.yml spring: datasource: hikari: maximum-pool-size: 5 # Small for dev # application-prod.yml spring: datasource: hikari: maximum-pool-size: 20 # Large for prod minimum-idle: 5 connection-timeout: 20000 idle-timeout: 300000
Without this tuning, your production database will be connection-starved.
Configuration mistakes often lead to performance and security problems:
npm install -g @joptimize/cli joptimize auth YOUR_API_KEY joptimize analyze .
JOptimize detects:
application.properties filesapplication.yml if using .properties@Validated
Get your free code audit:npm install -g @joptimize/cli joptimize auth YOUR_API_KEY joptimize analyze .
Master Spring Boot, security, and Java performance with hands-on courses.
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.