Skip to content
Chapter 11 · Spring Boot

Profiles: One Codebase, Many Environments ​

Chapter 10 ended with a question: two applications share one codebase; how do you create a bean in application 1 but not in application 2? @ConditionalOnProperty was the answer. Many people also answer "with @Profile". That works, but it is not what profiles are for. This chapter explains what profiling is first, then the @Profile annotation, and then comes back to the question.

Why profiles: one code, several environments ​

The same code runs in several environments, and each needs different configuration. Connecting to a database, for example:

EnvironmentWhereusernamepassword
devyour own machinedevUsernamedevPassword
qathe test (stage) machineqaUsernameqaPassword
prodlive productionprodUsernameprodPassword

Credentials are only one example. These differ too:

  • the URLs and ports of the services you call;
  • connection and request timeouts: a production machine is far more powerful than your laptop, so the values differ;
  • throttle limits and retry counts.

Until now there was one application.properties. How do you keep three sets of values in it and pick the right one? That is what profiles solve.

Step 1: one properties file per environment ​

In Spring Boot, a profile is a named environment, and each profile can have its own properties file named application-{profile}.properties:

text
src/main/resources/
├── application.properties          shared values and defaults
├── application-dev.properties
├── application-qa.properties
└── application-prod.properties
properties
# application.properties
db.username=defaultUsername
db.password=defaultPassword
properties
# application-dev.properties
db.username=devUsername
db.password=devPassword

application-qa.properties and application-prod.properties look the same with qa... and prod... values. (The profile names dev, qa, prod are your choice; the file name just has to match.)

A bean that reads the values:

java
@Component
public class DBConnection {

    @Value("${db.username}")
    String username;

    @Value("${db.password}")
    String password;

    @PostConstruct
    public void init() {
        System.out.println("username: " + username + " password: " + password);
    }
}

(Use a prefix such as db. for keys like these. A bare username key can be overridden by a USERNAME environment variable, which many systems set to your login name; environment variables win over property files, as chapter 9 showed.)

Start the application without saying which profile to use:

text
No active profile set, falling back to 1 default profile: "default"
username: defaultUsername password: defaultPassword

Creating the files does not activate anything. With no active profile, Spring Boot uses only application.properties.

Step 2: activate a profile ​

The property spring.profiles.active says which profile is active:

properties
# application.properties
db.username=defaultUsername
db.password=defaultPassword
spring.profiles.active=qa
text
The following 1 profile is active: "qa"
username: qaUsername password: qaPassword

The log line shows which profile is active. Spring Boot now loads application-qa.properties in addition to application.properties. Think of them as parent and child:

Key is in...Value used
both filesthe child's (the profile file wins)
only application.propertiesthe parent's
only the profile filethe child's

So application.properties holds the values shared by every environment, and each profile file holds only what differs.

Step 3: choose the profile when starting ​

Editing spring.profiles.active in a file for every deployment is impractical. Usually the file keeps a default (say dev) and the real choice is made when the application starts.

With Maven. When you press Run in the IDE, it runs the equivalent of:

bash
mvn spring-boot:run

Pass the profile as a property of the Spring Boot Maven plugin with -D:

bash
mvn spring-boot:run -Dspring-boot.run.profiles=prod

With spring.profiles.active=qa still in application.properties:

text
The following 1 profile is active: "prod"
username: prodUsername password: prodPassword

The value given at startup wins over the one in the file. If you give none, the file's value is used.

With Maven profiles in pom.xml. Many companies' pom.xml files hold named Maven profiles that set the same property:

xml
<profiles>
    <profile>
        <id>local</id>
        <properties>
            <spring-boot.run.profiles>dev</spring-boot.run.profiles>
        </properties>
    </profile>
    <profile>
        <id>production</id>
        <properties>
            <spring-boot.run.profiles>prod</spring-boot.run.profiles>
        </properties>
    </profile>
    <profile>
        <id>stage</id>
        <properties>
            <spring-boot.run.profiles>qa</spring-boot.run.profiles>
        </properties>
    </profile>
</profiles>
bash
mvn spring-boot:run -Pproduction

-P activates the Maven profile with id production, which sets spring-boot.run.profiles to prod: exactly the previous command, with a readable name. (Maven profiles and Spring profiles are different things; here one simply sets the other.)

With the packaged JAR, pass it as an argument or an environment variable:

bash
java -jar app.jar --spring.profiles.active=prod
SPRING_PROFILES_ACTIVE=prod java -jar app.jar

Step 4: @Profile, beans for one profile ​

Profiles can switch beans too, not just values. @Profile tells Spring Boot to create a bean only when a given profile is active:

java
@Component
@Profile("prod")
public class MySQLConnection {

    @Value("${db.username}")
    String username;

    @Value("${db.password}")
    String password;

    @PostConstruct
    public void init() {
        System.out.println("MySQL bean created, username: " + username + " password: " + password);
    }
}

@Component
@Profile("dev")
public class NoSQLConnection {
    ... the same, printing "NoSQL bean created" ...
}

With qa active (from application.properties):

text
The following 1 profile is active: "qa"

Nothing else. Both classes are singletons, so Spring would create them at startup, but first it checks @Profile: prod is not the active profile, and neither is dev. Neither bean is created.

With prod active (--spring.profiles.active=prod):

text
The following 1 profile is active: "prod"
MySQL bean created, username: prodUsername password: prodPassword

@Profile("prod") matches, so MySQLConnection is created, and it reads its values from application-prod.properties. NoSQLConnection (dev) is not created. A bean without @Profile is created whatever the profile.

@Profile also takes expressions: @Profile("!prod") (any profile except prod), @Profile("dev | qa"), @Profile("prod & cloud").

Step 5: several profiles at once ​

spring.profiles.active accepts a comma-separated list. Make NoSQLConnection @Profile("qa") and start with:

bash
java -jar app.jar --spring.profiles.active=prod,qa
text
The following 2 profiles are active: "prod", "qa"
MySQL bean created, username: qaUsername password: qaPassword
NoSQL(qa) bean created, username: qaUsername password: qaPassword
  • Beans: both @Profile("prod") and @Profile("qa") beans are created, since both profiles are active.
  • Properties: both application-prod.properties and application-qa.properties are loaded. For a key in both, the last profile listed (qa) wins, which is why both beans got qaUsername. A key only in the prod file would still apply.

Running in two environments at once rarely makes sense. Several profiles are mostly useful for switching several groups of @Profile beans on together.

Back to the question: one codebase, two applications ​

A bean in shared code should exist in application 1 but not in application 2. With profiles:

java
@Component
@Profile("app1")
public class NoSQLConnection { ... }

and spring.profiles.active=app1 in application 1, app2 in application 2. Application 2 never creates the bean. It works.

But a profile means an environment. app1 is not an environment: it would invite an application-app1.properties next to dev, qa and prod, and anyone reading spring.profiles.active=app1 would expect an environment. Choosing features per application is what @ConditionalOnProperty (chapter 10) is for.

@ConditionalOnProperty@Profile
Decided byany property and valuewhich profile (environment) is active
Meant forfeature switches, migrations, per-application beansenvironment-specific beans

Next: That completes configuration. Chapter 12 starts a new module: AOP, which runs extra code around method calls (logging, timing, transactions) without touching the methods themselves. Transactions and @Async, built on it, follow in chapters 13 to 17.

Check yourself ​

  1. Why do applications need profiles? Name three kinds of configuration that differ per environment.
  2. What must the properties file for a profile called qa be named?
  3. The profile files exist but nothing activates one. Which values are used, and what does the log say?
  4. application.properties has db.timeout=10 and db.username=defaultUsername; application-qa.properties has only db.username=qaUsername. With qa active, what are db.timeout and db.username?
  5. application.properties says spring.profiles.active=qa, and you run mvn spring-boot:run -Dspring-boot.run.profiles=prod. Which profile is active?
  6. What does mvn spring-boot:run -Pproduction do with the pom.xml shown in step 3?
  7. A bean has @Profile("dev") and qa is active. Is it created? And a bean with no @Profile?
  8. With spring.profiles.active=prod,qa, which beans are created, and which value wins for a key in both profile files?
  9. Can @Profile create a bean in application 1 but not application 2? Why is it still the wrong tool?
Answers
  1. The same code needs different configuration in each environment. For example credentials, service URLs and ports, timeouts, throttle limits, retry counts.
  2. application-qa.properties.
  3. Only application.properties; the log says "No active profile set, falling back to 1 default profile: "default"".
  4. db.timeout=10 (only in the parent) and db.username=qaUsername (the child wins).
  5. prod: the value given at startup wins over the file.
  6. It activates the Maven profile production, which sets spring-boot.run.profiles=prod, so the Spring profile prod is active.
  7. No. Yes, a bean without @Profile is created in every profile.
  8. Both the @Profile("prod") and @Profile("qa") beans; both files load, and for a key in both, qa (the last listed) wins.
  9. Yes, with @Profile("app1") and a different active profile per application. But profiles mean environments; per-application beans are a feature switch, which @ConditionalOnProperty is for.