A Suggestion on Managing Profiles in Spring Boot
Introduction
Spring Boot provides out-of-the-box support for managing different environments through what Spring calls profiles; dev, staging, test, prod, etc. Normally, each profile would load shared variables and configurations from the shared application.properties
file then from an environment-specific file, application-dev.properties
for example.
The Issue
More often than not, environments that are not production. dev, staging, test, share configurations that only differ with production for something such as logging level; usually, you’d set logging to debug on dev, staging, and test but limit it to info on production. This might leave us with repeated (think copy and paste) configurations across multiple files, and repeated code is usually a sign for potential improvement.
What We Can Do
Given how environment configuration is loaded within Spring Boot, one might be tempted to keep such configurations in the shared application.properties
and then overwrite them in the production environment, application-prod.properties
. This does solve the problem of repetition, but it leaves the ‘shared’ application.properties
in a weird state where it has all the shared configurations but not really; some of these configurations will be overwritten. More importantly, this approach risks “leaking” these configurations accidentally into production.
The Suggestion
While the previous solution works and one might think is encouraged (otherwise, why do Spring profiles work this way then), a cleaner approach would be to introduce a non-prod
profile that gets loaded into each non-production environment. This approach achieves the following:
1- Keeps the application.properties
file truly for shared properties across all environments.
2- Explicitly shows configurations that are meant for non-production development or testing purposes.
3- Minimizes the number of repeated configurations.
This benefit is, especially, apparent further down the deployment pipeline when environment variables need to be provided for say Docker orchestrators such as Kubernetes, Elastic Beanstalk, etc…
The Details
The specifics of how to, exactly, accomplish this suggestion for dev
, staging
and prod
profiles can be summarized as:
1- Create the following properties files:
* application.properties
* application-non-prod.properties
* application-dev.properties
* application-staging.properties
* application-prod.properties
2- To the non-production profiles, namelyapplication-dev.properties
, and application-staging.properties
add this line: spring.profiles.include=non-prod
. This tells Spring to include the non-prod
“profile” to the list of active profiles which makes all the properties in application-non-prod.properties
available to the application.
3- Follow these conventions:
* If a property will retain its value across all profiles → add it to application.properties
.
* If a property should only have a different value for production → add it to bothapplication-non-prod.propertie
and application-prod.properties
.
* If a property changes values per profile → add it to all profiles; applicatio-dev.properties
, application-staging.properties
and application-prod.properties
.
An Example Implementation
To create a minimal application to test this setup, we can start with an empty Spring Boot app with at least thespring-boot-web-starter
dependency and add the following to our profile files:
Sample 1: application.properties
prop1=This is a shared value across all profiles for prop1.
Sample 2: application-non-prod.properties
prop2=Non-production-specific value for prop2.
Sample 3: application-dev.properties
spring.profiles.include=non-prod
prop3=Development-specific value for prop3.
Sample 4: application-staging.properties
spring.profiles.include=non-prod
prop3=Staging-specific value for prop3.
Sample 5: application-prod.properties
prop2=Production-specific value for prop2.
prop3=Production-specific value for prop3.
Once these files are all set up, add an endpoint /properties
that accepts a key
and returns its value
from the environment for easy testing:
Sample 6: PropertyController.java
@RestController
@RequestMapping("/properties")
public class PropertyController {
private final Environment environment;
@Autowired
public PropertyController(
Environment environment) {
this.environment = environment;
}
@GetMapping
public String getProperty(@NotNull String key) {
return environment.getProperty(key);
}
}
Testing the dev
Profile
Starting the application in dev mode
To start the application with the dev
profile set as active, we can use the command:
mvn spring-boot:run -Dspring-boot.run.profiles=dev
Verifying Spring’s Logs
Once the command is run, Spring should display both dev
(the profile set by us) and non-prod
(the profile “included” with dev) as active profiles:
.s.s.SpringProfilesSuggestionApplication : The following profiles are active: dev,non-prod
Assuming the application is running locally on the default 8080
port, we can verify that properties were loaded as expected.
Verifying prop1
value
Issuing the curl command
curl localhost:8080/properties?key=prop1
should return:
This is a shared value across all profiles for prop1.
which verifies that shared properties from application.properties
were loaded properly.
Verifying prop2
value
And issuing the command:
curl localhost:8080/properties?key=prop2
should return:
Non-production-specific value for prop2
which verifies that non-production properties from application-non-prod.properties
were loaded properly and that’s the whole point on this setup.
Verifying prop3
value
While issuing the command:
curl localhost:8080/properties?key=prop3
should return:
Development-specific value for prop3
which verifies that specific-profile properties from application-dev.properties
were loaded properly.
Testing the prod Profile
Testing the prod
profile follows the exact same path as the dev
profile. This section might get monotonic but I want to lay it down for completeness.
Starting the application in prod mode
To start the application with the prod
profile:
mvn spring-boot:run -Dspring-boot.run.profiles=prod
Verifying Spring’s Logs
Spring should display the log:
.s.s.SpringProfilesSuggestionApplication : The following profiles are active: prod
Verifying prop1
value
Issuing the command:
curl localhost:8080/properties?key=prop1
should return:
This is a shared value across all profiles for prop1
Verifying prop2
value
While issuing the command:
curl localhost:8080/properties?key=prop2
should return:
Production-specific value for prop2
Verifying prop3
value
And issuing the command:
curl localhost:8080/properties?key=prop3
should, also, return:
Production-specific value for prop3
The Code
If you’d like a test drive with minimal effort, I’ve put together the same setup handy on GitHub: https://github.com/sayadi/spring-profiles-suggestion.