The Spring Framework is an application framework and inversion of control container for the Java platform. The framework’s core features can be used by any Java application, but there are extensions for building web applications on top of the Java EE (Enterprise Edition) platform. Although the framework does not impose any specific programming model, it has become popular in the Java community as an addition to, or even replacement, for the Enterprise JavaBeans (EJB) model. The Spring Framework is an open source.
Spring Profiles provide a way to segregate parts of your application configuration and make it available only in certain environments.
Spring profiles are a very useful concept in the framework but there are some gotchas to catch for mastering it. The following article aims to explain how profiles are applied and how to survive in a multi-profile set-up.
Let's start from the set-up
Everything starts from Application.java. This is a standard Spring boot command line application. Also introduce EnvironmentPrinter.java, this helps with print environment and properties.
Properties.java will be holding our properties that are defined into application.yml
Now everything is set up. Let us start the application and find out what happens:
No active profile set, falling back to default profiles: default
Active profiles:
Properties: Properties(param1=param1)
As we can see we don’t have an active profile. But application.yml is read and param1 has the value param1. Notice that because no active profiles have been set, spring falls back to default profile. Default profile is applied to all spring beans that do not explicitly use @Profile annotation.
Let's try to introduce default profile: dev
first, we create new yml for dev profile application-dev.yml
app:
param1: dev-param1
and add the following to application.yml:
spring:
profiles:
default: dev
Let's run our application again.
No active profile set, falling back to default profiles: dev
Active profiles:
Properties: Properties(param1=param1)
Okay, it seems correct. We do not have an active profile, so it falls back to dev profile… Wait a minute, why doesn't our param1 have value dev-param1?
Default profile makes our beans to be in dev profile, but it doesn’t load any additional properties files. So, instead of introducing the default profile, we should introduce the active profile. Replacing yml's relevant default profile block:
spring:
profiles:
active: dev
gives us an output:
The following profiles are active: dev
Active profiles: dev
Properties: Properties(param1=dev-param1)
Now it looks the way we expected as it should be.
Spring allows us to activate multiple profiles. I will introduce 2 more profiles, let's name them dev-db and dev-mock. And I will create 2 more yml files.
#application-dev-db.yml
app:
param1: dev-db-param1
#application-dev-mock.yml
app:
param1: dev-mock-param1
Now I will activate the dev-db profile in application.yml file. Let us ignore the dev-mock profile right now. We will use it soon.
#application.yml
spring:
profiles:
active: dev,dev-db
app:
param1: param1
We are now expected to have active profiles dev and dev-db. But which will be our param1 value? Let's run the application once again.
The following profiles are active: dev,dev-db
Active profiles: dev,dev-db
Properties: Properties(param1=dev-db-param1)
If I switch active profiles to dev-db,dev, the new output as follows.
The following profiles are active: dev-db,dev
Active profiles: dev-db,dev
Properties: Properties(param1=dev-param1)
So, we can conclude that if property is overridden in another profile, the last profile in the row wins.
Now let us try to activate one more profile but I want to do it in some active profile yml. In general, I will try to override spring.profiles.active property.
#application-dev-db.yml
spring:
profiles:
active: dev,dev-db,dev-mock
app:
param1: dev-db-param1
and output for that change
The following profiles are active: dev,dev-db
Active profiles: dev,dev-db
Properties(param1=dev-db-param1)
Hmm... okay, it seems that it is not possible to override active profiles definition.
Of course, we can add dev-mock to active profiles in application.yml but this is not always desired. Sometimes we want to have a certain profile include another profile. This can be done with include property. Lets modify dev profile yml a little bit.
The following profiles are active: dev,dev-db,dev-mock
Active profiles: dev,dev-db,dev-mock
Properties: Properties(param1=dev-mock-param1)
As we can see, a new dev-mock profile is included and applied as last in the row - so all the properties that dev-mock introduces, will be included and they will override previous profile properties with the same name.
Why to use include instead of modifying application.yml. Usually we do not want to introduce active profiles in application.yml or we will override profiles with command line argument -Dspring.profiles.active=. Command line override gives us the ability to introduce the exact profile per environment.
Sometimes one single profile yml might be too long or there are many repetitions with other environments. In this case, it is much more convenient to use single profile in the command line property and include the other profiles in that profile's yml file.
For example, local-dev might include dev,local-db,local-mock profiles to act like a dev environment but override database's and some third party API URL's.
In this case we introduce yml as follows:
#application-local-dev.yml
spring:
profiles:
include: dev,local-db,local-mock
And execute with command line parameter
"-Dspring.profiles.active=local-dev"
The following profiles are active: local-dev,dev,local-db,local-mock
Active profiles: local-dev,dev,local-db,local-mock
Properties: Properties(param1=dev-param1-override)
I didn't change application.yml, it still contains an active profile as dev,dev-db. But as you can see this is not applied. It is because of command line argument. So, If we use the command line argument, spring boot will ignore spring.profiles.active property in yml file.
This is a nice feature to know. This helps to configure the application so that it has a default active profile which will be used if nothing is specified. I usually use local-dev as default active profile and all other environments use command line parameter for overriding this profile.
In my experience, it is useful to keep running the app in the local environment as easy as possible because this is an environment that must be built up multiple times for different developers in different locations. Hassle-free local environment helps start coding as soon as possible.
Nobody wants to hear day after day that a professional developer must waste his/her time with setting up an environment.
So far, I played with profiles and their inclusions. I already showed how profiles override each other's properties. Now let us dig deeper.
Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order (with values from lower items overriding earlier ones)[https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config]:
As we can see the config data is number three in the list. So it's quite low in priority. In other words, we have plenty of options to override application properties.
I will not get into these because I want to keep the focus on yml files. After all, this is an article about profiles. So let's concentrate on the third bullet point.
Config data files are considered in the following order:
Right now, our yml files were in classpath. Let's assume that we have an application as jar and we want to override something without modifying our jar package. It’s quite simple, just add yml file to the same folder than jar.
I will demonstrate it with local-dev profile.
First, let's modify classpath application-local-dev.yml. I would like to keep it simple, so I cleaned it up and added only the param1 value.
#application-local-dev.yml
app:
param1: local-dev-param1
And our external local-dev yml (the file that I added to the same level as jar or out of the source directory, into the runtime directory)
#external application-local-dev.yml
app:
param1: local-dev-param1-override
Let's run the application now.
The following profiles are active: local-dev
Active profiles: local-dev
Properties: Properties(param1=local-dev-param1-override)
Everything is the way we expected and like spring documentation stated. External yml file property will override the same property in classpath file.
Let's restore the include part in classpath yml.
#application-local-dev.yml
spring:
profiles:
include: dev, local-db,local-mock
app:
param1: local-dev-param1
And now the output is follows:
The following profiles are active: local-dev,dev,local-db,local-mock
Active profiles: local-dev,dev,local-db,local-mock
Properties: Properties(param1=dev-param1)
We can see now that param1 is holding the value dev-param1. This might seem confusing, but it is logical if we see the list of active profiles. Even though the local-dev property was overridden in external yml file, the profile precedence is local-dev -> dev -> local-db -> local-mock. It means that dev is more important than local-dev and because of that it overrides external property.
Let's try to override spring profiles include. As I stated before, it might be that we cannot change jar file. So, we might want to change properties with an external yml file.
I added include property with some overridden profiles to yml
#external application-local-dev.yml
spring:
profiles:
include: nodev,local-mock,local-db
app:
param1: local-dev-param1-override
First, I changed dev to nodev profile. Secondly, I swapped local-mock and local-db. Lets see our output
The following profiles are active: local-dev,dev,local-db,local-mock,nodev
Active profiles: local-dev,dev,local-db,local-mock,nodev
Properties: Properties(param1=dev-param1)
Now it's interesting. First it seems that spring.profiles.include cannot be overridden. Active profiles are still local-dev -> dev -> local-db -> local-mock. It is clear that this order is the same as in classpath. But then there is nodev profile as our last and most relevant profile. It seems that we can add new profiles with include but we can't change the already declared profiles. This is important to know. Nothing hints that nodev should be with highest priority and it is easy to make mistakes with overridden properties. Although I wanted to declare that nodev must be lower than local-db or local-mock, then in reality it got higher precedence.
It might very well be that the person who must run the application and is responsible for overriding profiles with a external config might not know all the profiles, or the developer changes profiles in classpath without notice. Such misconfiguration might mess up the properties values and it might be very hard to in the end to debug errors in production.
As we could see, using spring-boot profiles is a very useful way to mitigate different properties values in different environments. Adding all the possible ways to override properties will give us an extremely dynamic tool to change their values. But it can get very messy quite easily. Because of this, I think that overriding properties should be avoided or used as little as possible. Try to arrange profiles so that new profiles introduce new properties but do not override them.
Some recommendations that might help you as they helped me:
Powerful tools deserve respect!