spring

Loading environment specific configurations and properties with Spring using Maven Profiles and Settings Example

In this example we shall show you how to load specific environment configurations and properties with Spring using Maven POM Profiles and XML SettingsOur previous example, shows how to load specific environment configurations and properties using Spring where these properties files are located inside the project resource folder.

However, sometimes we have a sensitive authentication information like database user name, passwords, etc., and uploading such piece of information on version control system (Git, SVN) may not be allowed. So, using Maven settings.xml file as an external properties values holder outside our project directory is a good approach where it will not be bundled with the project.

 
When Maven loads the project’s POM, it will pickup the given activated profile from the settings.xml file, and inject the properties declared within the profile in the corresponding POM profile.

Now, It’s the time for Spring magic which supports using PropertySourcesPlaceholderConfigurer configured with its environment-specific properties file. Now we can activate the desired environment Maven profile while we are building our application, that allows to load specific configurations beans and properties by deployment regions, such as “development”, “testing” and “production”, etc.

Let’s start our example below which shows how to use this feature where we have two environment-specific properties (Database, JMS) where each environment has different values for these properties. So, we need to load these properties at each environment.

1. Project Environment

  1. Spring 4.1.5
  2. Spring Test 4.1.5
  3. JUnit 4.12
  4. Apache Maven 3.0.5
  5. JDK 1.8
  6. Eclipse 4.4 (Luna)

2. Project Structure

We create a simple Spring Maven project with the following Structure.

project-structure
Figure 1: Project Structure

3. Maven Profiles with Settings.xml

We have the following three Maven profiles (dev, test and prod) inside our below POM file.

pom.xml:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.jcg.example</groupId>
    <artifactId>springmavenproperties-example-code</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>Spring Maven Properties Example Code</name>
 
    <properties>
 
        <!-- Generic properties -->
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <resource.directory>src/main/resources</resource.directory>
 
        <!-- Spring -->
        <spring-framework.version>4.1.5.RELEASE</spring-framework.version>
 
        <!-- Logging -->
        <log4j.version>1.2.17</log4j.version>
 
        <!-- Test -->
        <junit.version>4.12</junit.version>
 
    </properties>
 
    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>
 
        <!-- Logging with Log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
 
        <!-- Test Artifacts -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-framework.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
    <profiles>
        <profile>
            <id>dev</id>
            <!-- Dev Env. Properties -->
            <properties>
                <profile.name>${profile.name}</profile.name>
                <!-- Database Properties -->
                <db.driverClass>${db.driverClass}</db.driverClass>
                <db.connectionURL>${db.connectionURL}</db.connectionURL>
                <db.username>${db.username}</db.username>
                <db.password>${db.password}</db.password>
                <!-- JMS Properties -->
                <jms.factory.initial>${jms.factory.initial}</jms.factory.initial>
                <jms.provider.url>${jms.provider.url}</jms.provider.url>
                <jms.queue>${jms.queue}</jms.queue>
            </properties>
        </profile>
        <profile>
            <id>test</id>
            <!-- Test Env. Properties -->
            <properties>
                <profile.name>${profile.name}</profile.name>
                <!-- Database Properties -->
                <db.driverClass>${db.driverClass}</db.driverClass>
                <db.connectionURL>${db.connectionURL}</db.connectionURL>
                <db.username>${db.username}</db.username>
                <db.password>${db.password}</db.password>
                <!-- JMS Properties -->
                <jms.factory.initial>${jms.factory.initial}</jms.factory.initial>
                <jms.provider.url>${jms.provider.url}</jms.provider.url>
                <jms.queue>${jms.queue}</jms.queue>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <!-- Prod Env. Properties -->
            <properties>
                <profile.name>${profile.name}</profile.name>
                <!-- Database Properties -->
                <db.driverClass>${db.driverClass}</db.driverClass>
                <db.connectionURL>${db.connectionURL}</db.connectionURL>
                <db.username>${db.username}</db.username>
                <db.password>${db.password}</db.password>
                <!-- JMS Properties -->
                <jms.factory.initial>${jms.factory.initial}</jms.factory.initial>
                <jms.provider.url>${jms.provider.url}</jms.provider.url>
                <jms.queue>${jms.queue}</jms.queue>
            </properties>
        </profile>
    </profiles>
 
    <build>
 
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- specify UTF-8, ISO-8859-1 or any other file encoding -->
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
 
        <resources>
            <!-- Placeholders that are found from the files located in the configured
                resource directories are replaced with the property values found from the
                profile specific configuration file. -->
            <resource>
                <directory>${resource.directory}</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
 
    </build>
 
</project>

Also, we have the below settings.xml XML file, contains the previous POM profiles where each one contains the properties values.

Although, there are more than one elements which configure the core behavior of Maven like (servers, mirrors, proxies, profiles, activeProfiles, etc.), We will focus on profiles and activeProfiles which serve our topic.

settings.xml:

01
02
03
04
05
06
07
08
09
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
 
    <!-- Active Profile Section -->
    <activeProfiles>
        <activeProfile>dev</activeProfile>
    </activeProfiles>
 
    <profiles>
        <profile>
            <id>dev</id>
            <!-- Dev Env. Properties -->
            <properties>
                <profile.name>dev</profile.name>
                <!-- Database Properties -->
                <db.driverClass>com.mysql.jdbc.Driver</db.driverClass>
                <db.connectionURL>jdbc:mysql://localhost:3306/emp</db.connectionURL>
                <db.username>dev_usr</db.username>
                <db.password>dev_pss</db.password>
                <!-- JMS Properties -->
                <jms.factory.initial>
                  org.apache.activemq.jndi.ActiveMQInitialContextFactory
                </jms.factory.initial>
                <jms.provider.url>tcp://localhost:61616</jms.provider.url>
                <jms.queue>dev.queue</jms.queue>
            </properties>
        </profile>
        <profile>
            <id>test</id>
            <!-- Test Env. Properties -->
            <properties>
                <profile.name>test</profile.name>
                <!-- Database Properties -->
                <db.driverClass>com.mysql.jdbc.Driver</db.driverClass>
                <db.connectionURL>jdbc:mysql://192.168.1.2:3306/emp</db.connectionURL>
                <db.username>test_usr</db.username>
                <db.password>test_pss</db.password>
                <!-- JMS Properties -->
                <jms.factory.initial>
                  org.apache.activemq.jndi.ActiveMQInitialContextFactory
                </jms.factory.initial>
                <jms.provider.url>tcp://192.168.1.2:61616</jms.provider.url>
                <jms.queue>test.queue</jms.queue>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <!-- Prod Env. Properties -->
            <properties>
                <profile.name>prod</profile.name>
                <!-- Database Properties -->
                <db.driverClass>com.mysql.jdbc.Driver</db.driverClass>
                <db.connectionURL>jdbc:mysql://192.168.1.1:3306/emp</db.connectionURL>
                <db.username>prod_usr</db.username>
                <db.password>prod_pss</db.password>
                <!-- JMS Properties -->
                <jms.factory.initial>
                  org.apache.activemq.jndi.ActiveMQInitialContextFactory
                </jms.factory.initial>
                <jms.provider.url>tcp://192.168.1.1:61616</jms.provider.url>
                <jms.queue>prod.queue</jms.queue>
            </properties>
        </profile>
    </profiles>
</settings>

4. Properties File

We have the following properties file application.properties where we need to load specific-environment values for each property in that file.

application.properties:

01
02
03
04
05
06
07
08
09
10
# Database Properties
db.driverClass=${db.driverClass}
db.connectionURL=${db.connectionURL}
db.username=${db.username}
db.password=${db.password}
 
# JMS Properties
jms.factory.initial=${jms.factory.initial}
jms.provider.url=${jms.provider.url}
jms.queue=${jms.queue}

5. ApplicationProperties Spring Component

We create ApplicationProperties.java as a Spring component class annotated by @Component which will be a singleton bean, we can autowire it to get properties using getProperty(String propName).

ApplicationProperties.java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.jcg.prop;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.core.env.Environment;
 
/**
 * @author ashraf_sarhan
 *
 */
@Configuration
@PropertySources({ @PropertySource(value = "properties/application.properties", ignoreResourceNotFound = true) })
public class ApplicationProperties {
 
    @Autowired
    private Environment env;
 
    public String getProperty(String propName) {
        return env.getProperty(propName);
    }
 
}
  1. Register a Properties File via Java Annotations:
    Spring 3.1 also introduces the new @PropertySource annotation, as a convenient mechanism of adding property sources to the environment. This annotation is to be used in conjunction with Java based configuration and the @Configuration annotation:

    1
    2
    @Configuration
    @PropertySources({ @PropertySource(value = "properties/application.properties", ignoreResourceNotFound = true) })
  2. Using / Injecting Properties:
    Unlike Our previous example where we are using @Value for injecting a property, we will obtain the value of a property with the new Environment API:

    1
    2
    3
    4
    5
    6
    @Autowired
    private Environment env;
     
    public String getProperty(String propName) {
        return env.getProperty(propName);
    }
  3. Properties Wrappers:
    As you can notice that we have different properties type such Database, JMS, etc. So, for more organized properties management, we wrapped each type in a wrapper Spring components where all database properties were wrapped in the DatabaseProperties.java and all JMS properties were wrapped in the JmsProperties.java as well, that will lead to more clean and maintainable code where we can autowire them and getting any property value through its getter method and not by the property name. So, we can do any change in the property name in its wrapper class without breaking the code which consumes the changed property.

    As you notice that we have an init() annotated by @PostConstruct, this method will be executed after dependency injection is done to initialize properties values after beans instantiation.

    DatabaseProperties.java:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    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
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package com.jcg.prop;
     
    import javax.annotation.PostConstruct;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
     
    /**
     * @author ashraf_sarhan
     *
     */
    @Component
    public class DatabaseProperties {
     
        @Autowired
        private ApplicationProperties applicationProperties;
     
        private String driverClass;
     
        private String connectionURL;
     
        private String username;
     
        private String password;
     
        @PostConstruct
        private void init() {
            this.driverClass = applicationProperties
                    .getProperty(PropertiesConstants.DB_DRIVERCLASS);
            this.connectionURL = applicationProperties
                    .getProperty(PropertiesConstants.DB_CONNECTION_URL);
            this.username = applicationProperties
                    .getProperty(PropertiesConstants.DB_USERNAME);
            this.password = applicationProperties
                    .getProperty(PropertiesConstants.DB_PASSWORD);
        }
     
        public String getDriverClass() {
            return driverClass;
        }
     
        public String getConnectionURL() {
            return connectionURL;
        }
     
        public String getUsername() {
            return username;
        }
     
        public String getPassword() {
            return password;
        }
     
        @Override
        public String toString() {
            return "DatabaseProperties [driverClass=" + driverClass
                    + ", connectionURL=" + connectionURL + ", username=" + username
                    + ", password=" + password + "]";
        }
     
    }

    JmsProperties.java:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    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
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    package com.jcg.prop;
     
    import javax.annotation.PostConstruct;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
     
    /**
     * @author ashraf_sarhan
     *
     */
    @Component
    public class JmsProperties {
     
        @Autowired
        private ApplicationProperties applicationProperties;
     
        private String factoryInitial;
     
        private String providerUrl;
     
        private String queue;
     
        @PostConstruct
        private void init() {
            this.factoryInitial = applicationProperties
                    .getProperty(PropertiesConstants.JMS_FACTORY_INITIAL);
            this.providerUrl = applicationProperties
                    .getProperty(PropertiesConstants.JMS_PROVIDER_URL);
            this.queue = applicationProperties
                    .getProperty(PropertiesConstants.JMS_QUEUE);
        }
     
        public String getFactoryInitial() {
            return factoryInitial;
        }
     
        public String getProviderUrl() {
            return providerUrl;
        }
     
        public String getQueue() {
            return queue;
        }
     
        @Override
        public String toString() {
            return "JmsProperties [factoryInitial=" + factoryInitial
                    + ", providerUrl=" + providerUrl + ", queue=" + queue + "]";
        }
     
    }

Also, we have a supplementary class PropertiesConstants.java which contains properties keys constants which are used through the code.

PropertiesConstants.java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.jcg.prop;
 
/**
 * @author ashraf_sarhan
 *
 */
public class PropertiesConstants {
 
    // Database Properties Constants
    public static final String DB_DRIVERCLASS = "db.driverClass";
    public static final String DB_CONNECTION_URL = "db.connectionURL";
    public static final String DB_USERNAME = "db.username";
    public static final String DB_PASSWORD = "db.password";
 
    // JMS Properties Constants
    public static final String JMS_FACTORY_INITIAL = "jms.factory.initial";
    public static final String JMS_PROVIDER_URL = "jms.provider.url";
    public static final String JMS_QUEUE = "jms.queue";
 
}

Finally, to allow the autowiring of our ApplicationProperties.java Spring component, we created the following Spring context file.

app-context.xml:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
    xsi:schemaLocation="
 
    <!-- scans for annotated classes in the com.company package -->
    <context:component-scan base-package="com.jcg" />
 
    <!-- enables annotation based configuration -->
    <context:annotation-config />
 
</beans>

6. ApplicationProperties Unit Test

Now, it’s the time for testing the previous code. Let’s run our JUnit test class ApplicationPropertiesTest.java and see the output.

As you will see, we can running our unit test using two ways:

  1. Running the unit test while building the project with a specific profile by executing the following command line from inside the project directory where we can explicitly activate any profile through binding its id value using the -P CLI Maven option, This option takes an argument that is a comma-delimited list of profile-ids to use. When this option is specified, no profiles other than those specified in the option argument will be activated.
    1
    mvn clean install -Pdev -s settings.xml
  2. This time, we will run the unit test while building the project with a specific profile but we will implicitly activate our desired profile via the <activeProfiles> section in Maven settings.xml file, This section takes a list of elements, each containing a profile-id inside.
    1. Firstly, we will add the <activeProfiles> section in our settings.xml file.
      1
      2
      3
      4
      5
      6
      7
      <settings>
        ...
        <activeProfiles>
          <activeProfile>dev</activeProfile>
        </activeProfiles>
        ...
      </settings>
    2. Finally, we will execute the following command line from inside the project directory.
      1
      mvn clean install -s settings.xml
Tip
As you notice in both ways, We are binding settings.xml using the -s CLI Maven option, This option takes an argument that is a Maven settings absolute file path.

ApplicationPropertiesTest.java:

01
02
03
04
05
06
07
08
09
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
36
37
package com.jcg.test;
 
import junit.framework.TestCase;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import com.jcg.prop.DatabaseProperties;
import com.jcg.prop.JmsProperties;
 
/**
 * @author ashraf_sarhan
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/app-context.xml")
public class ApplicationPropertiesTest extends TestCase {
     
    @Autowired
    private DatabaseProperties databaseProperties;
     
    @Autowired
    private JmsProperties jmsProperties;
     
    @Test
    public void testApplicationProperties() {
         
        // Using application properties through properties wrappers
        System.out.println(databaseProperties.toString());
        System.out.println(jmsProperties.toString());
         
    }
     
}

Output:

cli-output
Figure 2: CLI Output

7. Download the Source Code of this example

This was an example on how to load environment configurations and properties with Spring.

Download
You can download the full source code of this example here: SpringMavenPropertiesExampleCode.zip

Ashraf Sarhan

Ashraf Sarhan is a passionate software engineer, an open source enthusiast, has a Bsc. degree in Computer and Information Systems from Alexandria University. He is experienced in building large, scalable and distributed enterprise applications/service in multiple domains. He also has a keen interest in JavaEE, SOA, Agile and Big Data technologies.
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ramsen
Ramsen
7 years ago

I get “Failed to load ApplicationContext” when running this code. How would I fix this?

Back to top button