When we are writing software that is deployed to different environments, we often have to create different configuration files for each environment. If we are using Maven, we can do this by using build profiles.
This blog post describes how we can create a build script that uses different configuration for development, testing, and production environments.
The requirements of our build process are:
- Each profile must have its own configuration file. The name of that configuration file is always config.properties.
- The configuration files must be found from the profiles/[profile name] directory.
- The development profile must be active by default.
Let’s start by taking a quick look at our example application.
The Example Application
The example application of this blog post has only one class that writes 'Hello World!' to a log file by using Log4j. The source code of the HelloWorldApp class looks follows:
import org.apache.log4j.Logger; public class HelloWorldApp { private static Logger LOGGER = Logger.getLogger(HelloWorldApp.class); public static void main( String[] args ) { LOGGER.info("Hello World!"); } }
The properties file that configures Apache Log4j is called log4j.properties, and it is found from the src/main/resources directory. Our log4j.properties file looks as follows:
log4j.rootLogger=DEBUG, R log4j.appender.R=org.apache.log4j.FileAppender log4j.appender.R.File=${log.filename} log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
Our Log4j configuration file looks pretty ordinary, but it has one thing that doesn’t really make any sense. The value of the log4j.appender.R.File property is ${log.filename}.
Our goal is to create a build script that replaces this placeholder with the actual log file path. But before we can create that build script, we have to create the profile specific configuration files. Let’s move on and find out how we can do that.
Creating the Profile Specific Configuration Files
Because we have to create a build script that uses different configuration in development, production, and test environments, we have to create three configuration files that are described in the following:
- The profiles/dev/config.properties file contains the configuration that is used in the development environment.
- The profiles/prod/config.properties file contains the configuration that is used in the production environment.
- The profiles/test/config.properties file contains the configuration that is used in the test environment.
These properties files configure the file path of the log file that contains the log of our example application.
The configuration file of the development profile looks as follows:
log.filename=logs/dev.log
The configuration file of the production profile looks as follows:
log.filename=logs/prod.log
The configuration file of the testing profile looks as follows:
log.filename=logs/test.log
We have now created the properties files that specify the location of our log file. Our next step is create a build script that replaces the placeholder found from the src/main/resources/log4j.properties file with the actual property value. Let’s see how we can do that.
Creating the Build Script
We can create a Maven build script that replaces the placeholder found from the src/main/resources/log4j.properties file with the actual property value by following these steps:
- Configure the development, production, and testing profiles.
- Configure the locations of the properties files that contains the configuration of each Maven profile.
- Configure the location of our resources and enable resource filtering.
First, we have configure the development, production, and testing profiles in our pom.xml file. We can do this by following these steps:
- Create the development profile and configure it to be active by default. Specify a property called build.profile.id and set its value to 'dev'.
- Create the production profile. Specify a property called build.profile.id and set its value to 'prod'.
- Create the testing profile. Specify a property called build.profile.id and set its value to 'test'.
We can finish these steps by adding the following XML to our pom.xml file:
<!-- Profile configuration --> <profiles> <!-- The configuration of the development profile --> <profile> <id>dev</id> <!-- The development profile is active by default --> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <!-- Specifies the build.profile.id property that must be equal than the name of the directory that contains the profile specific configuration file. Because the name of the directory that contains the configuration file of the development profile is dev, we must set the value of the build.profile.id property to dev. --> <build.profile.id>dev</build.profile.id> </properties> </profile> <!-- The configuration of the production profile --> <profile> <id>prod</id> <properties> <!-- Specifies the build.profile.id property that must be equal than the name of the directory that contains the profile specific configuration file. Because the name of the directory that contains the configuration file of the production profile is prod, we must set the value of the build.profile.id property to prod. --> <build.profile.id>prod</build.profile.id> </properties> </profile> <!-- The configuration of the testing profile --> <profile> <id>test</id> <properties> <!-- Specifies the build.profile.id property that must be equal than the name of the directory that contains the profile specific configuration file. Because the name of the directory that contains the configuration file of the testing profile is test, we must set the value of the build.profile.id property to test. --> <build.profile.id>test</build.profile.id> </properties> </profile> </profiles>
Second, we have to configure Maven to load the property values from the correct config.properties file. We can do this by adding the following XML to the build section of our POM file:
<filters> <!-- Ensures that the config.properties file is always loaded from the configuration directory of the active Maven profile. --> <filter>profiles/${build.profile.id}/config.properties</filter> </filters>
Third, we have to configure the location of our resources directory and enable resource filtering. We can do this by adding the following XML to the build section of our POM file:
<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> <filtering>true</filtering> <directory>src/main/resources</directory> </resource> </resources>
We have now configured our build script to replace the placeholders found from our resources (files that are found from the src/main/resources directory) with the actual property values. Let’s move on and find out what this really means.
What Did We Just Do?
We have now created a build script that replaces the placeholders found our resources with the property values found from the profile specific configuration file.
In other words, if we compile our project by running the command: mvn clean compile -P test at the command prompt, the log4j.properties file found from the target/classes directory looks as follows (the relevant part is highlighted):
log4j.rootLogger=DEBUG, R log4j.appender.R=org.apache.log4j.FileAppender log4j.appender.R.File=logs/test.log log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
As we can see, the placeholder that was the value of the log4j.appender.R.File property was replaced with the actual property value that was read from the profiles/test/config.properties file.
Let’s move on and summarize what we learned from this blog post.
Summary
This blog post has taught us two things:
- If we need to use different configuration files in different environments, using Maven profiles is one way to solve that problem.
- If we need to replace placeholders found from our resource files with the actual property values, we can solve this problem by using resource filtering.
Moved the example application to GitHub.
very helpfull, thanks
I created huge config.prop, works fine
Svetlana,
good to hear that I could help you!
Hi Petri , i don't exactly how much its relative but i m stuck in problem where i want to move all properties used under from pom.xml to pom.properties file i did it.but its not able to read it
Hi Sunny,
Use the Properties Maven Plugin. The Usage page describes how you can read the properties of your build from a file.
I created exactly the same project structure but the config.properties file could not be found:
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building java_cukes 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ java_cukes ---
[INFO]
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ java_cukes ---
[debug] execute contextualize
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.656s
[INFO] Finished at: Fri Sep 21 10:13:33 CEST 2012
[INFO] Final Memory: 3M/15M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:2.5:resources (default-resources) on proj
ect java_cukes: Error loading property file 'C:\Documents and Settings\.....\java
_cukes\profiles\dev\config.properties' -> [Help 1]
Any idea ? Regards.
Hi Javix,
as the error states, the Maven Resources plugin tries to look for the properties file from the file path 'C:\Documents and Settings\…..\java_cukes\profiles\dev\config.properties’ and cannot find it. If the file path is correct, I would move the project to a directory which does not have white space in its path. I am not sure if this is an issue anymore but I remember facing similar problems in the past. Let me know if this did the trick.
Hey Petri,
Just a quick thanks for pointing out this feature. Our Maven POM profiles were starting to make the POM quite full & bloated. Being able to split a lot of that environment specific configuration out into separate files is exactly what we needed (and thought wasn't possible via Maven).
So thanks a bunch!
Hi Lee,
It is great to hear that I could help you out.
Hello,
quick thx for this example, it helped me further to configured binaries.
I was wondering why you have duplicated each tag in the profiles.
I have moved the build tag out of the profiles.
Was there any particular reason for it?
kind regards
Hi,
Are you referring to the build.profile.id property? The reason why I specify it in each profile is that I think that it is cleaner than the solution described in this StackOverflow question.
If you know another solution, I would love to hear about it!
When the project is a web-app, I like to take the properties out of the application completely and put them inside the e.g. the Tomcat "context.xml" so the the app reads them from its environment, rather than having to remember to compile a different version.
In context.xml:
Read the values in the app:
JndiTemplate template = new JndiTemplate();
String sts = template.lookup("java:comp/env/Status");
Cheers!
You might be interested in this plugin as an alternative:
https://github.com/sofdes/config-generation-maven-plugin
Its very configurable but has sensible defaults. Available from Maven Central:-
com.ariht
config-generation-maven-plugin
0.9.10
generate/goal>
That plugin looks very interesting. I will have to take a closer look at it. Thanks for the tip!
Thanks a lot Petri. This is really very helpful page. Thanks for sharing this. I have one question with the described approach . will it be applicable to placeholders in xml files ? I have weblogic-properties where i m putting the placeholder that can be substituted as per the selected profile but its not working ?
Yes, you can use this approach with XML files as well. You just have to put your XML files to the correct directory (this example uses the src/main/resources directory). Also, if you include only specific files, you will have to change the configuration to include your XML file.
I have a few questions about your problem:
Hi Petri,
I have been reading your articles for the last week they are great! I tried to run this example though but the jar does not contain the log4j jar, do you know if I have to specify something else when I call the command?
Thanks
Hi,
Yes. You need to declare the Log4j dependency in your pom.xml file. Take a look at the
dependencies
section of this pom.xml file.Thanks, I declared and it compiled, but then to build it you need to also include the assembly plugin as you explain in another of your tutorials, after that it was all good, thanks a lot.
You are welcome. I am happy to hear that you were able to solve your problem.
Hi, tank you for this tutorial, it's very good, I had a small problem when i was running the project, maven was throwing a exception saying "FileNotFoundException: /Users/xxxx/Documents/codigo/web/main/resources/profiles/dev/config.properties" it was fixed changing this
profiles/${build.profile.id}/config.properties
for this
src/main/resources/profiles/${build.profile.id}/config.properties.
Hi Omar,
You are welcome!
The example assumes that the profiles directory is found from the root directory of the project. If you create the profiles directory to some other directory, you need to change the value of that property.
However, If you create the profiles directory under the src/main/resources directory, Maven will add all profile specific configuration files to the created binary (unless you exclude them).
God bless you for this tutorial, it helped me.
Thank you for your kind words. I really appreciate them.
Since in my case (as in yours) there are only three files, I just configured it as three separate files: profiles/${build.profile.id}.properties, instead of three separate directories.
It works great - very helpful, thanks!
Thank you for your kind words. I really appreciate them. Also, I agree that adding separate directories is not necessary if you have only a few profile specific files.
Hi Petri,
How can I use the property from the config file in java class ( ${} obviously is not working and also I am not using Spring).
Thanks in advance.
Hi Mar,
Take a look at this blog post. It explains how you can read property files from the file system and from the classpath.
Thank you very much !
Petri,
I am currently implementing the same logic in a new project and now I have problem that it couldn't read the property value -> it prints it as ${test}, not the value ot test property.
Do you have an idea what I do wrong or what I have forgotten to do?
Thanks
The problem was the type of the project when I create maven project with netbeans and when with eclipse, which is completely strange....anyway I resolved the issue.
Hi,
Good to hear that you were able to solve your problem.
Bro...I am also facing the same...did u get any help
Hi,
I have two profiles in my pom.xml and want to build both of them together. i.e. I want my application to build jboss7 as well as was8.5 profiles together. But when I run command, giving both profiles, only was8.5 gets executed as it is written in the end of pom.xml.
Is there any way by which I can generate both ear's together.
Hi Petri,
Very nice information on profiles in Maven. I have also one requirement where I have to change pom.xml at run time. Is it possible in any way?
My Scenario is
I have 2 xml file (testng1.xml and testng2.xml) and I am running 2 xml files via pom.xml but sometimes I have to run either 1 or 2 xml so as of now we are commenting another file and run.
So any idea or solution for this so that based on our parameter it can run required xml file.
Any help will be highly appreciated.
Hi,
It's a bit hard to answer to your question because I assume that the plugin you are using to run your unit tests might have support for your use case. Are you by any chance using the Maven Surefire plugin?
Thanks for this article, helped me in configuring Maven with Selenium.
You are welcome! I am happy to hear that this blog post was useful to you.
I tried using the example you provided but the final artifacts that were generated replaced the config.properties values in properties files defined in the main/resources folder within jar created by pom not in the zip file as intended in assembly.xml.
I am looking for replacing the contents of properties files defined in my main/resources folder with config.properties and the clean install to generate a zip file as defined in assembly.xml
Hi Petri
Thanks a lot for the post. It was very useful to me.
I used same approach as suggested in the post. And tests run in command line as I expect.
But, I'm not able to run any test in IDE (Intellij 2016). It gives "No tests were found. Empty test suite" error. If I comment out filters and testResources in pom, then IDE runs the tests.
I use surefire plugin.
Any idea what it could be?
thanks
is there any way in which we can encrypt this property file ?
I am using the above example. instead of logger file I am using server.port=8085 different in different environment files. But its not working. It always picks up default port.
It's hard to say what could be wrong because you didn't share your POM file and the property files, but you said that your Maven build always uses the default port. Where to do you specify this default port?
Also, does your "real" property file contain the following line:
I am also getting the similar issue sir...I am using dbConfig as main properties file it will get values from xxx.properties or yyy.properties......when I run I am getting value as ${port}...spend lot of time on this but no luck please help me sir
jfyi my pom.xml is same as urs....hoping my teammate for this from this url
Hi,
Unfortunately it's impossible to say what's wrong because I cannot see your POM file and your properties files. Could you create a demo project which allows me to reproduce your problem and add this project to Github (or Gitlab)?
Hello sir, Your blog is very useful i'm also working on Maven Thanks it will be beneficial for us.
Thank you for your kind words. I really appreciate them.
Petri Kainulainen
Thanks Alot
You are welcome.
Below is my requirement
I have 4 components with same profile and property file. These 4 components are not dependent on each other. There should be one common place to place the property files and accessible to these 4 components. The same property file copy should be used by the 4 components
Please suggest me the different ways to place/access the property files
thank you
Hi,
I assume that you want to use the properties file for configuring the application (and not for passing properties to the Maven module). If so, you should take a look at this StackOverflow answer.
Hi Petri,
Thanks for quick response.
But i am not looking for the application configuration. the property file is having database(JNDI) details which are common for all the four moduels.
Currently i added the property file in my module as below but cdt-model is not dependancy to my current module. here ${env} is the profile
../cdt-model/src/main/resources/${env}/CDT-${env}.properties
<!-- src/main/resources/${env}/CDT-${env}.properties -->
Suggest me different options for adding this property file.
What about the Test suite for these property file. How can we mock it
Hi,
I have typically created a new Maven profile that is activated when I run my tests. This way I can just create a test specific properties file and I don't have to mock anything.
Also, if you are using Spring Boot and you can annotate your tests with the
@ActiveProfiles
annotation, you can use the properties support provided by Spring Boot. For Example, if your active profile is called:integrationTest
, you can create a properties file calledapplication-integrationTest.properties
and Spring Boot uses this properties file when theintegrationTest
Spring profile is active (when you run your tests).what is in the logs/dev.log file and what is it for?