In any set of development frameworks, multi-environment management is usually one of the important core features, of course, in the Spring framework is no exception, here we call the Spring Profiles. This feature is simple to say, but it is easy to accidentally mess up the implementation, this article I intend to properly sort out some of the concepts to understand, the management up to not mess up.

Spring Boot

Create sample applications

  1. Quickly building a project using the Spring Boot CLI

    1
    
    spring init --dependencies=web --groupId=com.duotify sbprofile1
    

    You can also use Spring Initializr to create

    Open the project using Visual Studio Code.

    1
    
    code sbprofile1
    
  2. Add a HomeController controller

    File path: src/main/java/com/duotify/sbprofile1/controllers/HomeController.java.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    package com.duotify.sbprofile1.controllers;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HomeController {
        @GetMapping("/")
        public String home() {
            return "Hello World";
        }
    }
    
  3. Run the application

    1
    
    mvn clean spring-boot:run
    

    Addendum: You can add a new <defaultGoal>spring-boot:run</defaultGoal> setting under the <build> node in pom.xml, then you can just type mvn and it will automatically start Spring Boot running in the future! 👍

  4. Requesting applications using cURL

    1
    2
    
    $ curl localhost:8080
    Hello World: dev
    

Understand the true meaning of Profiles

Since the Spring framework has a bunch of abstract concepts, there are a lot of devilish details that you can’t understand if you don’t take the time to study them. The Spring Profiles mentioned in this article are originally a very simple concept, but there are so many variations when writing Spring Boot that you can get your head in a knot.

Let’s start with the simplest and most abstract concept.

The so-called Profile usually has a name (Profile Name), and this name represents a set of application configurations. You can quickly switch between application configurations with a simple Profile Name, it’s that simple!

The application configuration contains two layers of meaning.

  • Configuration is actually a property definition file like src/main/resources/application.properties.
  • Components combination means which “components” of the application are enabled, and you can decide what Profile to use to start the application during the running period with simple parameters.

The most common example of using a Profile to manage application configuration is for “multi-environment” deployments, such as when you have an internal “test environment” and a customer-provided “official environment”. The configuration of the two is usually different, but there are some similarities. At this point, we can manage these differences through multiple profiles. After abstraction, we can switch between the different environments as long as we know the Profile Name.

How to use Application Properties

Before you can understand how to manage multiple profiles, you should understand how Application Properties should be used.

The steps of the experiment are as follows.

  1. edit the src/main/resources/application.properties property file to add a my.profile property value

    1
    
    my.profile=dev
    
  2. Adjust the HomeController, add a Private Field, and inject a my.profile attribute value via the @Value annotation

    File path: src/main/java/com/duotify/sbprofile1/controllers/HomeController.java

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    package com.duotify.sbprofile1.controllers;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HomeController {
    
        @Value("${my.profile}")
        private String myProfile;
    
        @GetMapping("/")
        public String home() {
            return "Hello World: " + this.myProfile;
        }
    }
    
  3. Testing

    1
    
    mvn clean spring-boot:run
    

    Testing with cURL.

    1
    2
    
    $ curl localhost:8080
    Hello World: dev
    

How to pass in attribute values via Maven, command line parameters, environment variables, .env

Sometimes we want to add property values to the application during the “compilation period” via Maven, and then we need a way to pass the properties defined by Maven’s pom.xml into the src/main/resources/application.properties property file.

The steps of the experiment are as follows.

  1. edit the src/main/resources/application.properties property file.

    Add a my.profile property value.

    1
    
    my.profile=@my.profile@
    
  2. Adjust the pom.xml file and add a <my.profile> attribute to <properties>

    1
    
    <my.profile>dev2</my.profile>
    
  3. Testing

    1
    
    mvn clean spring-boot:run
    

    Testing with cURL

    1
    2
    
    $ curl localhost:8080
    Hello World: dev2
    

The @my.profile@ syntax in the application.properties properties file is unique in that it declares that you will read the property values from outside, and if Maven has defined properties, by default they will be added as default values when the project is compiled. This syntax also has the advantage of allowing you to Assign Value by various methods only during execution. For example.

  • Importing parameters directly from command line parameters

    1
    
    mvn clean spring-boot:run -Dmy.profile=dev3
    
    1
    2
    
    $ curl localhost:8080
    Hello World: dev3
    
  • Passing in attribute values directly from environment variables

    Here is the syntax for setting environment variables in Bash.

    1
    
    my_profile=dev4 mvn clean spring-boot:run
    
    1
    2
    
    $ curl localhost:8080
    Hello World: dev4
    

    When the environment variable encounters an attribute name with a decimal point (. ), remember to convert to underscores (_ ).

  • First package it as a JAR file, and when run by java -jar you can also pass in parameters via command line arguments.

    1
    
    mvn clean package
    
    1
    
    java -Dmy.profile=dev5 -jar target/sbprofile1-0.0.1-SNAPSHOT.jar
    

    This -Dmy.profile=dev5 parameter is passed to the JVM as a system parameter.

    1
    2
    
    $ curl localhost:8080
    Hello World: dev5
    

    Remember that -Dmy.profile=dev5 must be set in front of -jar!

  • First package it as a JAR file, and when run by java -jar you can also pass in parameters via environment variables.

    1
    
    mvn clean package
    
    1
    
    my_profile=dev6 java -jar target/sbprofile1-0.0.1-SNAPSHOT.jar
    
    1
    2
    
    $ curl localhost:8080
    Hello World: dev6
    

    Remember that -Dmy.profile=dev5 must be set in front of -jar!

  • Pass in the parameters directly from the environment variables defined by the .env file

    First, add a .env file to the project root directory with the following contents.

    1
    
    my_profile=dev7
    

    Create a .vscode/launch.json startup configuration file (VSCode).

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    {
        "version": "0.2.0",
        "configurations": [
            {
                "type": "java",
                "name": "Launch DemoApplication",
                "request": "launch",
                "mainClass": "com.duotify.sbprofile1.DemoApplication",
                "projectName": "sbprofile1",
                "envFile": "${workspaceFolder}/.env"
            }
        ]
    }
    

    The focus here is on the envFile setting.

    Press F5 to start the project, and you can read the settings!

    1
    2
    
    $ curl localhost:8080
    Hello World: dev7
    

How to use Spring Profiles

After understanding how to use and configure Properties files, we can finally get to the main point of this article, which is how to define Spring Profiles profiles.

Here are the steps of the experiment.

  1. edit src/main/resources/application.properties property file

    Add a spring.profiles.active attribute value.

    1
    
    spring.profiles.active=@spring.profiles.active@
    

    The spring.profiles.active on the left here is the name of the property that the Spring framework will use, while the @spring.profiles.active@ on the right is a property that can be passed in from external.

  2. Adjust Maven’s pom.xml configuration file and add the <profiles> section settings.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    <profiles>
    <profile>
        <id>default</id>
        <activation>
        <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
        <spring.profiles.active>default</spring.profiles.active>
        </properties>
    </profile>
    <profile>
        <id>dev8</id>
        <properties>
        <spring.profiles.active>dev8</spring.profiles.active>
        </properties>
    </profile>
    </profiles>
    

    What is special about this configuration is that we define 2 Profiles, one is our default profile and the other is the dev8 profile. However, each of the different profiles has a special spring.profiles.active attribute defined (you can define multiple attributes), which is used specifically to give Spring applications a reference as to who the currently enabled profiles are.

    Remember: The properties you define in pom.xml (<properties>) are not directly referenced by Java programs, they are related as follows.

    1
    
    Java raw file <-- Application properties file (.properties/.yml) <-- External incoming properties (Maven properties / environment variables / command line parameters)
    
  3. Modify the @Value annotation of HomeController to inject the spring.profiles.active property instead

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    package com.duotify.sbprofile1.controllers;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HomeController {
    
        @Value("${spring.profiles.active}")
        private String myProfile;
    
        @GetMapping("/")
        public String home() {
            return "Hello World: " + this.myProfile;
        }
    }
    
  4. Testing

    Remember that we now have two profiles, default and dev8. When we start the application with mvn spring-boot:run, we can use -P plus a ProfileName to enable the profile.

    1
    
    mvn clean spring-boot:run -Pdev8
    

    Note: The P in -P here must be capitalized, and the name that follows is the <id> element value in pom.xml!

    Testing with cURL

    1
    2
    
    $ curl localhost:8080
    Hello World: dev8
    

    If you try to pass in a non-existent dev9 profile name, you will get the default profile.

    1
    
    mvn clean spring-boot:run -Pdev9
    
    1
    2
    
    $ curl localhost:8080
    Hello World: default
    

    Note: It is possible to have two Profiles enabled at the same time, separated by a comma with -P. For example, you can test the enabled Profile name with the following command: mvn help:active-profiles -Pdev,prod

Switching different application property files via Spring Profiles

Another advantage of using the Spring framework’s Profiles feature is that instead of setting the properties in Maven’s pom.xml file, you can set the application properties in different .properties files by using a special naming convention. Please see the comments for the following file name specifications.

1
2
3
4
5
# This is the default application properties file. No matter which setting is enabled, it will load the properties in this file
application.properties

# This is the application property file that will be applied to the specific profile, only the enabled property file will be loaded into the file property
application-{ProfileName}.properties

Next we will experiment with the application of multiple profile.

  1. Let’s add a dev9 profile to the pom.xml file.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    <profiles>
    <profile>
        <id>default</id>
        <activation>
        <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
        <spring.profiles.active>default</spring.profiles.active>
        </properties>
    </profile>
    <profile>
        <id>dev8</id>
        <properties>
        <spring.profiles.active>dev8</spring.profiles.active>
        </properties>
    </profile>
    <profile>
        <id>dev9</id>
        <properties>
        <spring.profiles.active>dev9</spring.profiles.active>
        </properties>
    </profile>
    </profiles>
    
  2. In addition to application.properties, we create two additional application property files

    File 1: src/main/resources/application.properties (add a my.name attribute)

    1
    2
    3
    
    my.profile=@my.profile@
    spring.profiles.active=@spring.profiles.active@
    my.name=Will
    

    File 2: src/main/resources/application-dev8.properties (add a my.name attribute)

    1
    
    my.name=John
    

    File 3: src/main/resources/application-dev9.properties (empty content)

  3. Modify the @Value annotation of HomeController to inject the spring.profiles.active property instead

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    package com.duotify.sbprofile1.controllers;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HomeController {
    
        @Value("${spring.profiles.active}")
        private String myProfile;
    
        @Value("${my.name}")
        private String myName;
    
        @GetMapping("/")
        public String home() {
            return "Hello World: " + this.myName;
        }
    }
    
  4. Testing

    Remember that we now have 3 profiles, default, dev8 and dev9.

    First, try not specifying a Profile.

    1
    
    mvn clean spring-boot:run
    
    1
    2
    
    $ curl localhost:8080
    Hello World: Will
    

    Then, try to specify Profile dev8.

    1
    
    mvn clean spring-boot:run -Pdev8
    
    1
    2
    
    $ curl localhost:8080
    Hello World: John
    

    Finally, try to specify Profile dev9.

    1
    
    mvn clean spring-boot:run -Pdev9
    
    1
    2
    
    $ curl localhost:8080
    Hello World: Will
    

Loading different dependent packages via Spring Profiles

The configuration of Spring Profiles not only allows you to set “attributes”, but also allows you to load different <dependencies> dependent packages according to different profiles, such as loading different versions of the same package (testing old and new versions), or the same interface but different packages (different database drivers). This is really great! 👍

  • The following are examples of settings for different versions of the same package.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    <profile>
    <id>dev8</id>
    <dependencies>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.0</version>
        </dependency>
    </dependencies>
    <properties>
        <spring.profiles.active>dev8</spring.profiles.active>
    </properties>
    </profile>
    

    If you want to see the dependent package information after applying a different Profile, you can run the following command.

    1
    
    mvn dependency:tree -Pdev8
    
  • The following are examples of settings for same interface with different packages.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    <profiles>
    <profile>
        <id>Local</id>
        <dependencies>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.3.3</version>
            <classifier>jdk5</classifier>
        </dependency>
        </dependencies>
        <properties>
        <jdbc.url>jdbc:hsqldb:file:databaseName</jdbc.url>
        <jdbc.username>a</jdbc.username>
        <jdbc.password></jdbc.password>
        <jdbc.driver>org.hsqldb.jdbcDriver</jdbc.driver>
        </properties>
    </profile>
    <profile>
        <id>MySQL</id>
        <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
        </dependencies>
        <properties>
        <jdbc.url>jdbc:mysql://mysql.website.ac.uk:3306</jdbc.url>
        <jdbc.username>user</jdbc.username>
        <jdbc.password>1234</jdbc.password>
        <jdbc.driver>com.mysql.jdbc.Driver</jdbc.driver>
        </properties>
    </profile>
    </profiles>
    

Loading different Beans components via Spring Profiles

Under the Spring framework, all classes annotated with @Component are registered as Beans components, including, of course, classes annotated with @Configuration, since they inherit from the @Component interface.

However, you can simply add the @Profile annotation to the class to declare that Spring is going to load the class under a specific Profile.

Here is an example of how to use it.

  1. Create a UserService class

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    package com.duotify.sbprofile1.services;
    
    public class UserService {
        public UserService(String name) {
            this.name = name;
        }
        private String name;
        public String getName() {
            return name;
        }
    }
    
  2. Create a UserServiceDev class

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    package com.duotify.sbprofile1.services;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Profile;
    import org.springframework.stereotype.Component;
    
    @Component
    @Profile("dev")
    public class UserServiceDev {
        @Bean
        public UserService getUserService() {
            return new UserService("Dev");
        }
    }
    

    This UserServiceDev will only be executed by Spring when the dev profile is enabled.

  3. Create a UserServiceProd class

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    package com.duotify.sbprofile1.services;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Profile;
    import org.springframework.stereotype.Component;
    
    @Component
    @Profile("!dev")
    public class UserServiceProd {
        @Bean
        public UserService getUserService() {
            return new UserService("Prod");
        }
    }
    

    This UserServiceDev will only be executed by Spring when the non-dev profile is enabled.

  4. Modify the HomeController and inject the UserService service via “constructive”

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    package com.duotify.sbprofile1.controllers;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.duotify.sbprofile1.services.UserService;
    
    @RestController
    public class HomeController {
    
        @Value("${spring.profiles.active}")
        private String myProfile;
    
        @Value("${my.name}")
        private String myName;
    
        private UserService svc;
    
        public HomeController(UserService svc) {
            this.svc = svc;
        }
    
        @GetMapping("/")
        public String home() {
            return "Hello World: " + this.svc.getName();
        }
    }
    
  5. Modify pom.xml and add two more <profile> definitions

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    <profile>
    <id>dev</id>
    <properties>
        <spring.profiles.active>dev</spring.profiles.active>
    </properties>
    </profile>
    <profile>
    <id>prod</id>
    <properties>
        <spring.profiles.active>prod</spring.profiles.active>
    </properties>
    </profile>
    
  6. Testing

    Remember that we now have 5 profiles, namely default, dev8, dev9, dev and prod.

    Try to specify Profile dev.

    1
    
    mvn clean spring-boot:run -Pdev
    
    1
    2
    
    $ curl localhost:8080
    Hello World: Dev
    

    Try to specify Profile prod.

    1
    
    mvn clean spring-boot:run -Pprod
    
    1
    2
    
    $ curl localhost:8080
    Hello World: Prod
    

Another practical example can be found in the example provided here.

Because Maven Profiles are so extensive, there are many more uses that can be found in the Guide to Maven Profiles.

Reference https://blog.miniasp.com/post/2022/09/21/Mastering-Spring-Boot-Profiles