5 Observations on Spring Boot's Loading Precedence for Properties Files With Spring Cloud Config
If you have a medium.com membership, I would appreciate it if you read this article on medium.com instead to support me~ Thank You! 🚀
Intro
Do you externalize and centralize your configurations using Spring Cloud Config Server? Do you use Spring Profiles to manage your configurations for different deployment environments? Do you get confused about which configurations in your Spring Boot applications take the higher priority when you deploy them? Me too!
Join me in this article where I share my experience with using Spring Cloud Config in Spring Boot applications and the order of precedence for properties files when running the Spring Boot applications.
Note: This article will not go into detail on how to implement Spring Cloud Config Server and will require some knowledge in deploying Spring Boot applications. At the point of writing, Spring Boot Version = 2.7.0, Spring Cloud Version = 2021.0.3-SNAPSHOT, and Spring Cloud Config Version = 3.1.3-SNAPSHOT.
Background Context
Before we start, let’s align our knowledge on the concepts of configurations in Spring Boot applications as well as how Spring Cloud Config and Spring Profiles come into play.
The diagram above is a quick overview of how we can containerize a Spring Boot application and deploy it as a microservice. As you have noticed, when deploying the Spring Boot application, you can pass in properties to alter the behavior of the application for different deployment environments.
These properties are typically defined in properties files (application.yaml
or application.properties
) and they can be overridden by environment variables. An example would be connecting to different database instances in the development vs production environment as shown in the diagram below.
However, it can be tedious to manage the properties for different deployment environments. Hence, we use Spring Profiles to aid us.
Integrating with Spring Profiles
Spring Profiles allows us to define properties files categorized by “profiles”. When you are deploying your Spring Boot application, you will define the active Spring Profiles using environment variables to load the profile-specific properties (AKA deployment environment-specific properties). Here’s an example using the docker command.
docker run --env SPRING_PROFILES_ACTIVE='dev' spring-boot-app
Below is a simple illustration of how properties are loaded using Spring Profiles:
Additionally, Spring Profiles allows us to load multiple properties files. If you were to set your profiles as follows:
spring.profiles.active="dev"
→ This will loadapplication.yaml
andapplication-dev.yaml
properties files.spring.profiles.active="dev, local"
→ This will loadapplication.yaml
,application-dev.yaml
andapplication-local.yaml
properties files.
Externalized Configurations with Spring Cloud Config
Internal Configurations is where you store the properties files within the Spring Boot applications in the classpath
. These properties files are packaged within the application, hence we can also call them packaged files.
classpath
refers to the location where resource files are stored and in a Maven project, the standard location for storing the application’s resources is in thesrc/main/resources
folder.
An issue with internal configurations is that every time a change to the configuration is required, you will have to re-build the container and re-deploy the application as shown below.
This issue can be simplified using externalized configurations where we override the properties using Java System Properties, Environment variables, Command-Line arguments, or external properties files.
Spring Cloud Config is an approach that allows you to store your properties files externally and retrieves them when the Spring Boot application starts. Hence, we will only need to re-deploy the application to reflect the configuration changes as shown below.
With these, we have covered the basic background context on Spring Boot properties, Spring Profiles, and Spring Cloud Config, let’s dive deeper into understanding the order of precedence for properties files that are loaded from different sources.
How do I configure my Spring Cloud Config Server?
I am using Git to store my configuration data. The diagram below is the folder structure for my configuration data where {application}
is a placeholder that refers to the Spring Boot application’s name configurable by the property spring.application.name
.
The Spring Cloud Config Server is configured to connect to a single repository with the configuration data stored in the config-repo
folder. Additionally, there are 2 search paths (subfolders) within the config-repo
folder.
The shared
subfolder is where all the common configurations shared across all Spring Boot applications are stored and each Spring Boot application has its own {application}
subfolder.
Looking into the order of precedence
Let’s get started! You can refer to the order of precedence for externalized configuration in Spring Boot’s documentation. Here, I will try to demonstrate the common scenarios for configuring externalized configurations in Spring Boot.
To frame the context for the demonstration, I will have a Spring Boot application (config-client) with 2 Spring Profiles (dev, local), a Spring Cloud Config Server, and several properties files as defined below.
There are 3 main locations for the properties files that I have configured.
- The
classpath
root - src/main/resources (Internal) - Spring Cloud Config Server (External)
- A
/config
folder (External)
The Spring Boot application will load the properties from these locations and add them to the Spring Environment.
Normal Deployments with Spring Profiles
Using a deployment scenario for the demonstration setup, let’s deploy the Spring Boot application with the Spring Profiles dev
and local
using the command below.
docker run \
--env SPRING_PROFILES_ACTIVE='dev, local' \
--env SPRING_CONFIG_ADDITIONAL_LOCATION='/config/' \
--volume <path_to_config_folder>:/config \
config-client
When the Spring Boot application starts, it will load the properties files in the following order of precedence (highest to lowest):
- Config Dir:
config/application-local.yaml
(profile-specific) - Config Dir:
config/application-dev.yaml
(profile-specific) - Config Dir:
config/application.yaml
(default) - Config Server:
config-client/config-client-local.yaml
(profile-specific) - Config Server:
shared/application-local.yaml
(shared, profile-specific) - Config Server:
config-client/config-client-dev.yaml
(profile-specific) - Config Server:
shared/application-dev.yaml
(shared, profile-specific) - Classpath:
resources/application-local.yaml
(profile-specific) - Classpath:
resources/application-dev.yaml
(profile-specific) - Config Server:
config-client/config-client.yaml
(default) - Config Server:
shared/application.yaml
(shared, default) - Classpath:
resources/application.yaml
(default)
What are the key observations about the order of precedence?
Based on the preceding example and my findings from the documentation, I have identified 5 key observations.
1 - Properties vs Yaml files
.properties
files have a higher priority than .yaml
files as mentioned in the Spring’s documentation. It is also highly recommended to stick to a single file format.
2 - Spring Profiles
Profile-specific properties files always override non-specific properties files. Eg. application-{profile}.yaml
> application.yaml
.
In the event of multiple Spring Profiles, a last-win strategy is applied where the order of precedence for properties files is from the right to the left
. Eg. application-local.yaml
> application-dev.yaml
for Spring Profiles spring.profiles.active="dev, local"
.
3 - Config Server Search Paths Configurations
The configuration for search paths in the Spring Cloud Config Server (spring.cloud.config.server.git.search-paths
) applies a last-win strategy. This means that the properties in the latter folders will have a higher priority. Using my configuration above, the priorities for the properties files will be as such config-repo/{application} > config-repo/shared
.
4 - How Config-Client loads Properties from Spring Cloud Config Server
With reference to Spring Boot 2.4 onwards, the Spring Cloud Config Client documentation recommends configurations from the Spring Cloud Config Server to be imported via the spring.config.import
property. If we were to take a look at Spring Boot’s blog update on config file processing in Spring 2.4, you will find the following statement:
Imports can be considered as additional documents inserted just below the document that declares them. They follow the same top-down ordering as regular multi-document files: An import will only be imported once, no matter how many times it is declared.
This means that properties files from Spring Cloud Config Server are resolved and prioritized against properties files in the same location group as the “document” that declares them. Moreover, properties files from Spring Cloud Config Server will have a higher priority than the “document” due to the way it is imported.
In our example above, we declared the property spring.config.import: <Config Server URL>
in the Spring Boot application’s classpath
(resources/application.yaml
). Hence, all properties files from the Spring Cloud Config Server and classpath will be prioritized and resolved under the same location group in the following order (highest to lowest):
- profile-specific properties files (
config-server
) - profile-specific properties files (
classpath
) - default properties files (
config-server
) - default properties files (
classpath
)
However, strangely enough, ALL profile-specific properties files (in config-server) that are imported via spring.config.import
has a higher priority compared to the profile-specific properties files in the classpath
. I believe this is the intended behavior for externally imported files.
5 - Externalized Properties Files
Referencing the Spring Boot’s Config Data Migration Guide, external files always override packaged files (profile-specific or not). Hence, you will notice the new change where the property spring.config.location
defines the following default location groups with the latter one having a higher priority.
optional:classpath:/;optional:classpath:/config/
optional:file:./;optional:file:./config/;optional:file:./config/*/
In our example above, all properties files in the /config
external folder have a higher priority compared to the Spring Cloud Config Server or classpath
. The properties files are prioritized and resolved within each location group in the following order (highest to lowest)
/config
folder (External) →spring.config.additional.location
- Spring Cloud Config Server (External) >
classpath
(Internal) → same location group as explained in point 4 above
On a side note, if the
spring.config.import
property for Spring Cloud Config Server was defined in the external folder/config/application.yaml
, then the order of precedence would change since/config
and Spring Cloud Config Server will be in the same location group.
Summary
That’s it! These are the 5 key observations that I have identified as I am working with Spring Boot and Spring Cloud Config. Depending on the way you bind the Spring Cloud Config Server to the Spring Config Client, the order of precedence may change, but the concepts should remain the same.
Do note that I am using a very fundamental setup with multiple profile-specific properties files. There are also many other approaches that you can use such as multi-document properties files, profile groups, profile activations, or Kubernetes Config Maps which were introduced in Spring Boot 2.4.
As for the Spring Cloud Config Server, you can also configure it to load from multiple git repositories or Vault. Do check all these cool features out on your own.
Lastly, I don’t think this article is anything new for people who are experienced with Spring Boot and Spring Cloud Config, but I do hope that it will be useful for people who are new to Spring Cloud Config :)
Refer to the Git Repo below for the code reference.
Thank you for reading till the end! ☕
If you enjoyed this article and would like to support my work, feel free to buy me a coffee on Ko-fi. Your support helps me keep creating, and I truly appreciate it! 🙏