Spring Boot Profiles and Environment-Specific Configuration
Introduction
This tutorial explains profiles in Spring Boot - named sets of configuration, such asdev and prod, that let the same application run with different settings and beans in each environment. We will start with what a profile is and write profile-specific configuration files. We will then activate profiles in the ways Spring Boot offers - a property, a command-line argument and an environment variable - and see how values are merged, including what happens when two active profiles set the same key. Next we will use @Profile to switch beans, group profiles together, and put several profiles into one YAML file. Lastly, we will see a common mistake that Spring Boot refuses at startup, activate profiles in tests, and list best practices.(The configuration files and their order of precedence are explained in application.properties vs application.ymlClick to read tutorial: application.properties vs application.yml in Spring Boot;
@Profile on beans was introduced in @Qualifier, @Primary and conditional beansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans.)
What is a profile?
A real application runs in several environments - a developer's laptop, a test server, production - and some settings must differ between them: the database URL, whether to send real emails, whether caching is on. A profile is a name for one of those environments. You write the settings that differ into files named after the profile, and when the application starts with that profile active, Spring Boot loads those files on top of the common ones. The same jar then behaves correctly everywhere, without a rebuild.Profile-specific configuration files
A file namedapplication-{profile}.properties (or .yml) is loaded only when {profile} is active. The common file holds the defaults for every environment -
application.properties - settings for every environment
shop.name=JavaBrahman Books
shop.database-url=jdbc:h2:mem:shop
shop.cache-enabled=false
shop.region=GLOBAL
shop.payments=sandbox
application-dev.properties - only when the dev profile is active
shop.database-url=jdbc:h2:mem:devshop
shop.banner=*** DEVELOPMENT - test data only ***
application-prod.properties - only when the prod profile is active
shop.database-url=jdbc:postgresql://db.internal:5432/shop
shop.cache-enabled=true
shop.payments=live
shop.region=US
application-eu.properties - settings for the European deployment
shop.region=EU
shop.currency=EUR
An application that prints its profiles, settings and profile-specific bean
//ProfilesApp.java
package com.javabrahman.boot.profiles;
import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
interface MailSender {
String describe();
}
@Component
@Profile("prod")
class SmtpMailSender implements MailSender {
public String describe() {
return "SmtpMailSender (real emails)";
}
}
@Component
@Profile("!prod")
class LoggingMailSender implements MailSender {
public String describe() {
return "LoggingMailSender (emails written to the log only)";
}
}
@SpringBootApplication
public class ProfilesApp {
public static void main(String[] args) {
try (var context = SpringApplication.run(ProfilesApp.class, args)) {
Environment env = context.getEnvironment();
System.out.println("active profiles = " + Arrays.toString(env.getActiveProfiles())
+ (env.getActiveProfiles().length == 0 ? " (default profiles " + Arrays.toString(env.getDefaultProfiles()) + ")" : ""));
for (String key : new String[] {"shop.database-url", "shop.cache-enabled", "shop.payments", "shop.region", "shop.currency", "shop.banner"}) {
String value = env.getProperty(key);
if (value != null) {
System.out.printf("%-19s= %s%n", key, value);
}
}
System.out.println("mail sender = " + context.getBean(MailSender.class).describe());
}
}
}
Activating profiles
The standard property isspring.profiles.active, a comma-separated list of profile names. Like any property, it can be set in a file, as a JVM system property, as an environment variable or on the command line. First with no profile, then with dev, then with prod -
OUTPUT with no profile activeactive profiles = [] (default profiles [default]) shop.database-url = jdbc:h2:mem:shop shop.cache-enabled = false shop.payments = sandbox shop.region = GLOBAL mail sender = LoggingMailSender (emails written to the log only)
active profiles = [dev] shop.database-url = jdbc:h2:mem:devshop shop.cache-enabled = false shop.payments = sandbox shop.region = GLOBAL shop.banner = *** DEVELOPMENT - test data only *** mail sender = LoggingMailSender (emails written to the log only)
active profiles = [prod] shop.database-url = jdbc:postgresql://db.internal:5432/shop shop.cache-enabled = true shop.payments = live shop.region = US mail sender = SmtpMailSender (real emails)
Explanation of the output
- With no active profile, Spring Boot uses the default profile, named
default, and onlyapplication.propertiesapplied. (A file namedapplication-default.propertieswould be loaded in this case;spring.profiles.defaultchanges which profiles count as default.) - With
dev,application-dev.propertieswas loaded on top of the common file: it replaced the database URL and added a banner, and every other setting kept its common value. Profile files only need to contain what differs. - With
prod, the production values replaced four settings, and@Profile("prod")switched the mail sender to the SMTP implementation.@Profile("!prod")made the logging sender the choice everywhere else, so exactly oneMailSenderbean existed in each run.
Several active profiles: the last one wins
More than one profile can be active at once - for example the environment (prod) and the region (eu). Both application-prod.properties and application-eu.properties set shop.region, so the order matters -
OUTPUT with --spring.profiles.active=prod,euactive profiles = [prod, eu] shop.database-url = jdbc:postgresql://db.internal:5432/shop shop.cache-enabled = true shop.payments = live shop.region = EU shop.currency = EUR mail sender = SmtpMailSender (real emails)
active profiles = [eu, prod] shop.database-url = jdbc:postgresql://db.internal:5432/shop shop.cache-enabled = true shop.payments = live shop.region = US shop.currency = EUR mail sender = SmtpMailSender (real emails)
eu file. For the one key both files set, shop.region, the profile listed last won: EU in the first run, US in the second. Design profiles so that they rarely set the same key, and when they do, document the intended order.
Activating a profile with an environment variable
On servers and in containers, profiles are usually activated with theSPRING_PROFILES_ACTIVE environment variable - relaxed binding maps it to spring.profiles.active - so that the start command stays the same everywhere -
set SPRING_PROFILES_ACTIVE=dev java -jar shop.jar
active profiles = [dev] shop.database-url = jdbc:h2:mem:devshop shop.cache-enabled = false shop.payments = sandbox shop.region = GLOBAL shop.banner = *** DEVELOPMENT - test data only *** mail sender = LoggingMailSender (emails written to the log only)
Dockerfile or a Kubernetes deployment, the variable goes in the container's environment; in a Maven run, ./mvnw spring-boot:run -Dspring-boot.run.profiles=dev does the same.
Profile groups
As the number of profiles grows, activating the right combination gets error-prone. A profile group, defined in the common configuration file, gives one name to several profiles. Running the same application with an extra group definition on the command line - OUTPUT with the group production=prod,eu activatedactive profiles = [production, prod, eu] shop.database-url = jdbc:postgresql://db.internal:5432/shop shop.cache-enabled = true shop.payments = live shop.region = EU shop.currency = EUR mail sender = SmtpMailSender (real emails)
production also activated its members, prod and eu, in that order. In a real project the group is a line in application.properties, such as spring.profiles.group.production=prod,eu,metrics; it is passed on the command line here only to keep the example's files unchanged.
Several profiles in one YAML file
Instead of one file per profile, a single YAML file can hold several documents, separated by---. A document that starts with spring.config.activate.on-profile applies only when that profile is active (.properties files support the same with a #--- separator line) -
multi.yml - common settings and two profile documents in one file
shop:
database-url: jdbc:h2:mem:shop
payments: sandbox
region: GLOBAL
---
spring:
config:
activate:
on-profile: dev
shop:
banner: "*** DEVELOPMENT - from multi.yml ***"
---
spring:
config:
activate:
on-profile: prod
shop:
database-url: jdbc:postgresql://db.internal:5432/shop
payments: live
active profiles = [prod] shop.database-url = jdbc:postgresql://db.internal:5432/shop shop.payments = live shop.region = GLOBAL mail sender = SmtpMailSender (real emails)
prod document applied; the dev document was skipped, so there is no banner. One file per profile is easier to find and to deploy separately; one multi-document file keeps small differences side by side. Both are fine - choose one style per project.
A mistake Spring Boot refuses
It is tempting to activate one profile from inside another profile's file. Spring Boot does not allowspring.profiles.active in a profile-specific file, because the set of active profiles has to be known before profile-specific files can be chosen - and it says so at startup -
application-qa.properties - wrongly tries to activate another profile
shop.database-url=jdbc:h2:mem:qashop
spring.profiles.active=dev
Starting the application with the qa profile
//QaStartup.java
package com.javabrahman.boot.profiles;
import org.springframework.boot.SpringApplication;
public class QaStartup {
public static void main(String[] args) {
try (var context = SpringApplication.run(ProfilesApp.class, "--spring.profiles.active=qa")) {
System.out.println("Started");
} catch (Exception e) {
System.out.println(e.getClass().getSimpleName() + ":\n " + e.getMessage());
}
}
}
InvalidConfigDataPropertyException: Property 'spring.profiles.active' imported from location 'class path resource [application-qa.properties]' is invalid in a profile specific resource [origin: class path resource [application-qa.properties] - 2:24]
2:24). Because the failure happens before logging is configured, Spring Boot also prints the whole stack trace, which is left out above. The fix is a profile group: spring.profiles.group.qa=dev in the common file activates dev whenever qa is active.
Profiles in tests
Tests often need their own settings - an in-memory database, fake external services.@ActiveProfiles activates profiles for one test class, which then loads an application-test.properties from src/test/resources along with @Profile("test") beans -
Activating a profile in a Spring Boot test
@SpringBootTest
@ActiveProfiles("test")
class CheckoutServiceTests {
// runs with application.properties + application-test.properties
}
Best practices
- Keep the common file complete and the profile files small: a profile file should contain only what differs.
- Prefer configuration over code: switch settings with profiles, and use
@Profilebeans only when the behaviour itself differs, as with the mail sender. Avoidif (environment.acceptsProfiles(...))branches in business code. - Do not put secrets in profile files that are committed to source control. Supply passwords and keys through environment variables or a secrets manager, and let the profile file refer to them with placeholders such as
${DB_PASSWORD}. - Name profiles after environments or capabilities (
dev,prod,eu,metrics), and use groups for combinations. - Make the default safe: with no profile active, the application should never charge real cards or send real emails - as
shop.payments=sandboxand the logging mail sender ensure here.
Summary
In this tutorial we first saw what a profile is and wrote profile-specific configuration files on top of a commonapplication.properties. We activated profiles with a command-line argument and an environment variable, saw the default profile, and watched @Profile choose between two mail senders. We then activated two profiles at once and saw that the last one wins for a shared key, grouped profiles under one name, and put several profiles into one multi-document YAML file. Lastly, we saw Spring Boot refuse spring.profiles.active inside a profile-specific file, activated profiles in tests, and listed best practices. The next tutorial covers what an application tells you while it runs: logging in Spring BootClick to read tutorial: Logging in Spring Boot: SLF4J, Logback and Log Levels.Spring Boot Tutorials
Type-Safe Configuration with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties Logging in Spring Boot: SLF4J, Logback and Log LevelsClick to read tutorial: Logging in Spring Boot: SLF4J, Logback and Log Levels Your First Spring Boot Application with Spring InitializrClick to read tutorial: Your First Spring Boot Application with Spring Initializr How Spring Boot Auto-Configuration WorksClick to read tutorial: How Spring Boot Auto-Configuration Works Spring Boot Starters Explained with ExamplesClick to read tutorial: Spring Boot Starters Explained with Examples application.properties vs application.yml in Spring BootClick to read tutorial: application.properties vs application.yml in Spring Boot Spring Boot DevTools: Automatic Restart and ProductivityClick to read tutorial: Spring Boot DevTools: Automatic Restart and Productivity
Type-Safe Configuration with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties Logging in Spring Boot: SLF4J, Logback and Log LevelsClick to read tutorial: Logging in Spring Boot: SLF4J, Logback and Log Levels Your First Spring Boot Application with Spring InitializrClick to read tutorial: Your First Spring Boot Application with Spring Initializr How Spring Boot Auto-Configuration WorksClick to read tutorial: How Spring Boot Auto-Configuration Works Spring Boot Starters Explained with ExamplesClick to read tutorial: Spring Boot Starters Explained with Examples application.properties vs application.yml in Spring BootClick to read tutorial: application.properties vs application.yml in Spring Boot Spring Boot DevTools: Automatic Restart and ProductivityClick to read tutorial: Spring Boot DevTools: Automatic Restart and Productivity