application.properties vs application.yml in Spring Boot

Introduction

This tutorial compares the two formats Spring Boot accepts for its configuration file - application.properties and application.yml. We will start with where Spring Boot looks for these files, then write the same configuration in both formats and load each one in a real application. We will then see what happens when both files exist, how lists and maps are written in each format, and the YAML pitfall that turns the country code NO into false. Next we will override values from outside the application, with environment variables and command-line arguments, and look at relaxed binding, which lets one property be written in several forms. Lastly, we will see the full order of precedence and decide which format to use.

(This tutorial uses @Value and ${...} placeholders from SpEL and @ValueClick to read tutorial: Spring Expression Language (SpEL) and @Value Explained; the next tutorial binds whole groups of properties with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties.)

Where Spring Boot looks for configuration files

At startup, Spring Boot looks for a file named application.properties or application.yml (or .yaml) in these places, and loads every one it finds -
  • the root of the classpath - in a Maven project, src/main/resources, so the file is packaged inside the jar;
  • a config package on the classpath;
  • the current directory, where the application is started;
  • a config/ sub-directory of the current directory, and its immediate sub-directories.
Files outside the jar override files inside it, so the packaged file can hold the defaults and an operations team can place an application.properties next to the jar to change them. The base name can be changed with spring.config.name, and extra files can be added with spring.config.location or spring.config.import. The examples below use spring.config.name so that each one can load its own file.

The same settings in both formats

Here are the settings of an online bookshop - a name, a page size, a list of currencies, nested contact details, and a map of delivery days per country - first as a .properties file -
shop-props.properties - the settings in .properties format
# Shop settings in .properties format: one key=value pair per line
shop.name=JavaBrahman Books
shop.page-size=20
shop.currencies=USD,EUR,INR
shop.contact.email=orders@example.com
shop.contact.phone=+44 20 7946 0000
shop.delivery-days.GB=2
shop.delivery-days.IN=5
shop.delivery-days.US=4
and then as a YAML file -
shop-yaml.yml - the same settings in YAML format
# Shop settings in YAML format: nesting by indentation
shop:
  name: JavaBrahman Books
  page-size: 20
  currencies:
    - USD
    - EUR
    - INR
  contact:
    email: orders@example.com
    phone: "+44 20 7946 0000"
  delivery-days:
    GB: 2
    IN: 5
    US: 4
A small Spring Boot application prints the settings, whichever file it loaded. It reads single values from the Environment, and uses Spring Boot's Binder to read the list and the map as Java collections -
A Spring Boot application that prints the shop settings it loaded
//ConfigPrinter.java
package com.javabrahman.boot.config;

import java.util.List;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;

@SpringBootApplication
public class ConfigPrinter {
  public static void main(String[] args) {
    try (var context = SpringApplication.run(ConfigPrinter.class, args)) {
      Environment env = context.getEnvironment();
      Binder binder = Binder.get(env);
      print("shop.name", env.getProperty("shop.name"));
      print("shop.page-size", env.getProperty("shop.page-size", Integer.class));
      print("shop.currencies", binder.bind("shop.currencies", Bindable.listOf(String.class)).orElse(null));
      print("shop.contact.email", env.getProperty("shop.contact.email"));
      print("shop.contact.phone", env.getProperty("shop.contact.phone"));
      print("shop.delivery-days", binder.bind("shop.delivery-days", Bindable.mapOf(String.class, Integer.class)).orElse(null));
      print("shop.countries", binder.bind("shop.countries", Bindable.listOf(String.class)).orElse(null));
      print("shop.countries-quoted", binder.bind("shop.countries-quoted", Bindable.listOf(String.class)).orElse(null));
    }
  }

  static void print(String key, Object value) {
    if (value != null) {   // print only the keys the loaded file sets
      System.out.printf("%-22s = %s%n", key, value instanceof Map<?, ?> || value instanceof List<?> ? value : "'" + value + "'");
    }
  }
}
Loading the .properties file, then the YAML file -  OUTPUT with --spring.config.name=shop-props
shop.name              = 'JavaBrahman Books'
shop.page-size         = '20'
shop.currencies        = [USD, EUR, INR]
shop.contact.email     = 'orders@example.com'
shop.contact.phone     = '+44 20 7946 0000'
shop.delivery-days     = {GB=2, IN=5, US=4}
 OUTPUT with --spring.config.name=shop-yaml
shop.name              = 'JavaBrahman Books'
shop.page-size         = '20'
shop.currencies        = [USD, EUR, INR]
shop.contact.email     = 'orders@example.com'
shop.contact.phone     = '+44 20 7946 0000'
shop.delivery-days     = {GB=2, IN=5, US=4}

Explanation of the code

  • Both files produced exactly the same settings. Spring Boot turns both formats into the same flat property names, such as shop.contact.email; the application code never knows which format was used.
  • In .properties, nesting is written with dots in every key. In YAML, it is written once, by indentation, so related settings are grouped visually and the shop. prefix is not repeated.
  • The comma-separated shop.currencies=USD,EUR,INR and the YAML list with - items both bound to a List of three strings.
  • The delivery-days keys became the keys of a Map, and their values were converted to Integer.
  • The phone number is quoted in YAML. Unquoted, a value starting with + and containing spaces is still read as a string, but quoting values with special characters avoids surprises - as the next section but one shows.

When both files exist

If the same location holds both a .properties and a .yml file with the same name, Spring Boot loads both, and for a key that appears in both, the .properties value wins -
both.properties
shop.name=Name from both.properties
both.yml
shop:
  name: Name from both.yml
  page-size: 50
 OUTPUT with --spring.config.name=both
shop.name              = 'Name from both.properties'
shop.page-size         = '50'
The name came from both.properties, while page-size, which only the YAML file sets, still came from both.yml: the files are merged key by key. This works, but it is confusing to maintain - keep one format per project.

The YAML pitfall: the Norway problem

YAML has an inconvenient rule inherited from version 1.1 of the YAML specification, which the SnakeYAML library that Spring Boot uses follows: the unquoted words yes, no, on and off, in any capitalisation, are booleans. That includes NO, the country code of Norway -
norway.yml - a list of country codes, unquoted and quoted
shop:
  countries: [GB, NO, IN]
  countries-quoted: ["GB", "NO", "IN"]
 OUTPUT with --spring.config.name=norway
shop.countries         = [GB, false, IN]
shop.countries-quoted  = [GB, NO, IN]

Explanation of the output

  • In the unquoted list, YAML read NO as the boolean false, which Spring then converted to the string "false" - Norway silently disappeared from the list. No error, no warning.
  • With quotes, "NO" stayed a string.
  • The same rule hits values such as on/off switches, version numbers like 1.10 (read as the number 1.1), and strings with leading zeros. Quote any YAML value that is not obviously a word or a plain number. In a .properties file, every value is a string until Spring converts it, so this problem does not exist.
The square-bracket list [GB, NO, IN] is YAML's inline "flow" style; it means the same as the indented list with - items used earlier.

Overriding settings from outside the application

A packaged application usually needs different settings in each environment. Spring Boot reads properties from several property sources besides the configuration files, and the outer sources win. The two most common are environment variables and command-line arguments. The run below starts the application with the .properties file, two environment variables and one command-line argument -
set SHOP_NAME=Name from an environment variable
set SHOP_PAGESIZE=40
java -jar shop.jar --spring.config.name=shop-props --shop.name="Name from the command line"
 OUTPUT with two environment variables and a command-line argument
shop.name              = 'Name from the command line'
shop.page-size         = '40'
shop.currencies        = [USD, EUR, INR]
shop.contact.email     = 'orders@example.com'
shop.contact.phone     = '+44 20 7946 0000'
shop.delivery-days     = {GB=2, IN=5, US=4}

Explanation of the output

  • shop.name was set in three places - the file, the SHOP_NAME environment variable and the command line - and the command-line argument won.
  • shop.page-size was set in the file (20) and by the SHOP_PAGESIZE environment variable (40), and the environment variable won.
  • Everything else still came from the file.
  • Command-line arguments are the --name=value options after the class or jar name; Spring Boot turns them into properties.

Relaxed binding

Environment variables cannot contain dots or dashes on most systems, so how did SHOP_PAGESIZE set shop.page-size? Spring Boot applies relaxed binding: it accepts several spellings of the same property name -
  • shop.page-size - kebab case, the recommended form in .properties and .yml files;
  • shop.pageSize and shop.page_size - camel case and underscore notation, also accepted in files;
  • SHOP_PAGESIZE - the environment-variable form: upper case, dots replaced by underscores, and dashes removed. List items use the index as a segment, as in SHOP_CURRENCIES_0_=USD.
Relaxed binding applies to Spring Boot's own properties too, so SERVER_PORT=9090 changes server.port, and SPRING_PROFILES_ACTIVE=prod activates a profile. In code, always refer to the canonical kebab-case name, for example @Value("${shop.page-size}").

The order of precedence

Spring Boot's property sources form a list, and a value from a source higher in the list overrides the same key lower down. Simplified, from highest to lowest -
  1. Properties set by tests, such as @SpringBootTest(properties = ...) and @TestPropertySource.
  2. Command-line arguments.
  3. The JSON in SPRING_APPLICATION_JSON, and servlet parameters in web applications.
  4. JVM system properties (-Dname=value).
  5. Operating-system environment variables.
  6. Configuration files: profile-specific files (such as application-prod.yml) above plain ones, and files outside the jar above files inside it.
  7. @PropertySource annotations on configuration classes, and defaults set with SpringApplication.setDefaultProperties().
The rule of thumb: the closer to the moment of starting the application, the stronger. Defaults go in the packaged file, environment-specific values in environment variables or an external file, and one-off overrides on the command line. Profile-specific files are covered in Spring Boot ProfilesClick to read tutorial: Spring Boot Profiles and Environment-Specific Configuration.

Which format should you use?

  • YAML is more readable for deep, structured configuration - many nested settings, lists of objects, several profiles in one file (separated by ---). Its cost is whitespace sensitivity and the type-guessing pitfalls above.
  • .properties is flat and unambiguous: every line stands alone, values are never reinterpreted, and a key can be searched for as one string. It is easy to edit with scripts and hard to break.
  • Both support the same features - placeholders, lists, maps, multiple documents in one file (.properties uses a #--- line as the separator) and profile-specific variants.
  • Pick one per project and stick to it. Spring Initializr generates application.properties; renaming it to application.yml and converting the contents is all it takes to switch.

Summary

In this tutorial we first saw where Spring Boot looks for application.properties and application.yml. We wrote the same settings in both formats and loaded each with a real application, getting identical results, including a list and a map. We then saw that when both files exist they are merged and .properties wins, and watched YAML turn Norway's country code into false. Next we overrode the file with an environment variable and a command-line argument, explained relaxed binding, and listed the order of precedence of property sources. Lastly, we compared the two formats. The next tutorial binds these settings to a typed Java object: type-safe configuration with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties.