Spring Boot Essentials

Application Properties

18 min Lesson 6 of 13

Application Properties

Every non-trivial Spring Boot application needs to behave differently in different environments: a laptop developer database is not the production database, a test mail server is not the live SMTP relay, and debug-level logging is not appropriate in production. Spring Boot solves this through a unified, layered configuration system built around application.properties and its YAML sibling application.yml. This lesson covers how that system works, what you can configure, and the patterns experienced developers rely on every day.

Where Spring Boot Looks for Configuration

Spring Boot resolves configuration from multiple sources and merges them in a well-defined precedence order (higher number wins):

  1. Default property values coded inside auto-configurations
  2. application.properties / application.yml inside the packaged JAR (on the classpath root, i.e. src/main/resources/)
  3. Profile-specific files: application-{profile}.properties on the classpath
  4. Same files outside the JAR, in the working directory
  5. OS environment variables
  6. JVM system properties (-Dserver.port=9090)
  7. Command-line arguments (--server.port=9090)
Key insight: Command-line arguments and environment variables override everything below them in the list. This means your JAR ships with sane defaults in src/main/resources/application.properties, and ops can override any value at runtime without recompiling.

Properties vs YAML — Which to Choose

.properties files use flat key-value pairs:

spring.datasource.url=jdbc:postgresql://localhost:5432/shop spring.datasource.username=app spring.datasource.password=secret spring.datasource.hikari.maximum-pool-size=10

.yml (YAML) files express the same information as a nested tree:

spring: datasource: url: jdbc:postgresql://localhost:5432/shop username: app password: secret hikari: maximum-pool-size: 10

Both are equivalent. YAML is more readable for deeply nested structures and supports lists cleanly; .properties is simpler to grep and is familiar to most Java veterans. Spring Boot 3 supports both. Do not mix them in the same project — pick one and be consistent.

YAML gotcha: YAML is whitespace-sensitive. A stray tab (instead of spaces) or wrong indentation silently produces a different value or a startup failure. Use an IDE with YAML support and enable schema validation.

Commonly Used Spring Boot Properties

Spring Boot ships with thousands of configuration keys documented in its Common Application Properties reference. The ones you will touch most often are:

Server

server.port=8080 server.servlet.context-path=/api server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=30s

DataSource and JPA

spring.datasource.url=jdbc:postgresql://localhost:5432/shop spring.datasource.username=app spring.datasource.password=secret spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=false spring.jpa.open-in-view=false

Logging

logging.level.root=INFO logging.level.com.example.shop=DEBUG logging.file.name=logs/app.log

Spring MVC

spring.mvc.format.date=iso spring.mvc.format.date-time=iso spring.web.resources.add-mappings=false

Binding Properties to a Configuration Class

Scattering @Value("${my.key}") annotations throughout your codebase is fragile and hard to test. The idiomatic Spring Boot 3 approach is @ConfigurationProperties: you define a plain Java record or class, annotate it, and Spring binds the matching prefix from application.properties directly into the object — with type conversion, validation, and IDE autocompletion included.

# application.properties app.mail.host=smtp.example.com app.mail.port=587 app.mail.from=noreply@example.com app.mail.rate-limit=100
package com.example.shop.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.DefaultValue; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @ConfigurationProperties(prefix = "app.mail") public record MailProperties( @NotBlank String host, @DefaultValue("587") int port, @NotBlank String from, @Min(1) int rateLimit ) {}

Register it in your main application class (or any @Configuration class):

@SpringBootApplication @EnableConfigurationProperties(MailProperties.class) public class ShopApplication { public static void main(String[] args) { SpringApplication.run(ShopApplication.class, args); } }

Inject it anywhere with a plain constructor parameter (Spring Boot auto-wires it as a bean):

@Service public class MailService { private final MailProperties mail; public MailService(MailProperties mail) { this.mail = mail; } public void send(String to, String subject, String body) { // mail.host(), mail.port(), mail.from() are all type-safe System.out.printf("Sending from %s via %s:%d%n", mail.from(), mail.host(), mail.port()); } }
Why records work perfectly here: @ConfigurationProperties on a record uses the canonical constructor, so every property is required unless annotated with @DefaultValue. The result is an immutable, null-safe configuration object you can inject and test without mocking any Spring context.

Profiles — Environment-Specific Configuration

Profiles let you maintain separate configuration sets for dev, test, and prod without touching a single line of Java. Create a profile-specific file:

  • application-dev.properties — developer settings (H2 in-memory DB, verbose logging)
  • application-prod.properties — production settings (PostgreSQL, log level INFO, no SQL echo)
# application-dev.properties spring.datasource.url=jdbc:h2:mem:devdb;DB_CLOSE_DELAY=-1 spring.datasource.username=sa spring.datasource.password= spring.jpa.show-sql=true logging.level.root=DEBUG
# application-prod.properties spring.datasource.url=jdbc:postgresql://db.internal:5432/shop spring.datasource.username=${DB_USER} spring.datasource.password=${DB_PASS} spring.jpa.show-sql=false logging.level.root=WARN

Activate a profile at runtime — no recompile required:

# JVM flag java -jar shop.jar -Dspring.profiles.active=prod # Environment variable (useful in containers) SPRING_PROFILES_ACTIVE=prod java -jar shop.jar
Never commit secrets to source control. The pattern ${DB_PASS} inside application-prod.properties tells Spring Boot to resolve the value from an OS environment variable named DB_PASS. Use this for all passwords, API keys, and tokens. Secrets in .properties files that are checked into Git are a serious security risk.

The @Value Annotation — When It Still Makes Sense

@Value is useful for injecting a single property into a bean when a full @ConfigurationProperties class would be overkill:

@Component public class AppInfo { @Value("${app.version:unknown}") private String version; public String getVersion() { return version; } }

The :unknown suffix is the default value if the key is missing. Use @Value sparingly — it bypasses type safety and is harder to test. Prefer @ConfigurationProperties for any group of related settings.

Summary

Spring Boot's configuration system is layered: defaults in the JAR, overrides in profile-specific files, final overrides via environment variables and command-line arguments. You write shared settings in application.properties, environment-specific settings in application-{profile}.properties, and bind groups of related properties into type-safe @ConfigurationProperties records. Secrets never live in files — they come from the environment at runtime. Master this system and you will rarely need to change compiled code just to adjust application behaviour.