Configuring applications with Spring

If you’ve used Spring before, you’ve almost definitely used a PropertyPlaceholderConfigurer to inject settings from external sources — most likely properties files — into your application context. The most common use cases include JDBC and Hibernate settings, but it’s not that uncommon to also configure Lucene index, temp file, or image cache directories as well. The simplest case looks something like this:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="classpath:application.properties"/>
</bean>

<!-- A sample bean that needs some settings. -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

And application.properties might look like this:

jdbc.driver=org.h2.Driver
jdbc.url=jdbc:h2:mem:example
jdbc.username=sa
jdbc.password=

Note, you can achieve the same simple configuration using the new spring 2.x style schema configuration, but it doesn’t allow for any further customization so we’re going to use the old style.

<!-- Example of new Spring 2.x style -->
<context:property-placeholder location="classpath:application.properties"/>

This handles the simple case of replacing placeholders (e.g. ${jdbc.url}) with values found in a properties files (e.g. jdbc.url=jdbc:h2:mem:example). In a real-world application, we not only need to collect settings, but also override them in different environments. Many of our applications are deployed in 4 or more environments (developer machine, build server, staging server, and production), each requiring different databases at the very least.

There are a few ways to enable overriding of properties. Let’s take a look at them in turn:

1. Setting the system properties mode to override (default is fallback)

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
    <property name="location" value="classpath:application.properties"/>
</bean>

When configured in this mode, any value specified as a system property to the JVM will override any values set in properties files. For example, adding -Djdbc.url=jdbc:h2:mem:cheesewhiz to the JVM arguments would override the value in the file (jdbc:h2:mem:example). On a Java 1.5 or newer platform, Spring will also look for an environment variable called jdbc.url is no system property was found.

2. Specifying an optional properties file

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreResourceNotFound" value="true"/>
    <property name="locations">
        <list>
            <value>classpath:application.properties</value>
            <value>classpath:local.properties</value>
        </list>
    </property>
</bean>

When ignoreResourceNotFound is set to true, Spring will ignore resources that don’t exist. You can imagine application.properties, containing all of the default settings, versioned in your SCM system. Developers have the option of creating a properties file called local.properties to override any settings that differ in their environment. This file should be unversioned and ignored by your SCM system. This works because properties are loaded in order and replace previous values.

3. Web Application overrides

In a web application environment, Spring also supports specifying values in web.xml as context params or in your application server specific meta-data as servlet attributes. For example, if you’re using Tomcat you can specify one or more parameter elements in your context.xml, and Spring will can inject those values into placeholders.

<bean class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer">
    <property name="location" value="classpath:application.properties"/>
</bean>

The ServletContextPropertyPlaceholderConfigurer conveniently works in non servlet environments by falling back to the behavior of a PropertyPlaceholderConfigurer. This is great when running unit tests.

4. Combining techniques

There’s no reason why these techniques can’t be combined. Technique #1 is great for overriding a few values while #2 is better for overriding many. #3 just expands the field of view when Spring goes to resolve placeholders. When combined, system properties override those in files. When using technique #3, there are some settings available for adjusting the override behavior (see contextOverride). Test the resolution order when combining to ensure it’s behaving as expected.

Optional External Properties

There’s another use case that applies to some projects. Often in non-developer environments, system admins want to keep properties for the environment outside of the deployable archive or the application server, and they don’t want to deal with keeping those files in a Tomcat context file; they prefer a simple properties file. They also don’t want to have to place the file in a hard-coded location (e.g. /var/acmeapp/application.properties) or they may keep configuration for multiple servers in the same network directory, each file names after the server. With a little trickery, it’s easy to support an optional external properties file that isn’t in a hard-coded location. The location of the file is passed as a single system property to the JVM, for example: -Dconfig=file://var/acmeapp/server1.properties. Here’s the configuration to make it happen:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreUnresolvablePlaceholders" value="true"/>
</bean>

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreResourceNotFound" value="true"/>
    <property name="location" value="${config}"/>
</bean>

The first definition enables basic property resolution through system properties (in fallback mode). The second bean loads the resource from the location resolved from the system property -Dconfig. All spring resource urls are supported, making this very flexible.

Putting it all together

Here’s a configuration that does more than most people would need, but allows for ultimate flexibility:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreUnresolvablePlaceholders" value="true"/>
</bean>

<bean class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer">
    <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
    <property name="searchContextAttributes" value="true"/>
    <property name="contextOverride" value="true"/>
    <property name="ignoreResourceNotFound" value="true"/>
    <property name="locations">
        <list>
            <value>classpath:application.properties</value>
            <value>classpath:local.properties</value>
            <value>${config}</value>
        </list>
    </property>
</bean>

Every placeholder goes through the following resolution process. Once a value is found it’s set and the next placeholder is resolved:

  1. (optional) Property value specified as a system or environment property; useful for overriding specific placeholders (e.g. -Djdbc.host=devdb / -Djdbc.username=carbon5)
  2. (optional) Context parameters located in web.xml or context attributes specified in application server meta-data (e.g. a Tomcat context.xml).
  3. (optional) Properties file located by the system/environment variable called “config”; useful for externalizing configuration. All URL types are supported (e.g. -Dconfig=c://hmc.properties).
  4. (optional) Properties file identified by classpath:local.properties; useful for specific developer overrides.
  5. (required) Properties file identified by classpath:application.properties, which contains default settings for our application.

Best Practices

  • Deploy the same exact artifact (e.g. war, ear, etc) across all environments by externalizing configuration. This may seem daunting, but the emergent benefits are huge in terms of simplicity.
  • Only make things that can safely change across environments configurable. Also, only things that need to be configurable should be configurable, it’s easy to go overboard.
  • Configure the minimal properties search path that meets your requirements.
  • When looking for properties files in the project tree, use classpath resources whenever possible. This makes finding those files easy, consistent, and insensitive to the working-dir, which is great when running tests from your IDE and command line.
  • Aim for a zero-configuration check-out, build, run-tests cycle for the environment where its happens most: development.

What other interesting configuration scenarios have you seen?

About Christian Nelson

Christian is a software developer, technical lead and agile coach. He's passionate about helping teams find creative ways to make work fun and productive. He's a partner at Carbon Five and serves as the Director of Engineering in the San Francisco office. When not slinging code or playing agile games, you can find him trekking in the Sierras and playing with his daughters.
This entry was posted in Web and tagged . Bookmark the permalink.

16 Responses to Configuring applications with Spring

  1. Alex Cruikshank says:

    Thanks for posting this, Christian. It helped me solve some configuration problems I was having with my company’s CMS solution. One small drawback of the last example is that it requires the ${config} variable to be set even when the default configuration is sufficient. This is because the ${config} variable will still be unresolved when the second Configurer is instantiated and that Configurer is not set to ignore unresolved properties. A simple workaround is to add:

    <property name=”properties”>
      <props><prop key=”config”></prop></props>
    </property>

    to the ServletContextPropertyPlaceholderConfigurer definition.

  2. Alon Salant says:

    Finally, a (relatively) straight forward way to support default config for runtime and test with optional overrides. Nice.

  3. Nice post. I like what you say about deploying the same artifact across all environments. Besides externalizing properties, good old-fashioned JNDI is pretty nice in a lot of cases, such as externalizing JDBC DataSources and JavaMail Sessions. Anyway thanks for this post.

  4. christian says:

    While JNDI does provide a mechanism for looking up resources, it has its limitations. For example: if your unit tests have configuration settings that must be overridden, having to bring up a any part of the JEE stack to run your tests is far from ideal. Also, the multi-level overriding provide by these examples, which come straight from real world projects, isn’t possible with JNDI on its own.

    It’s been years since we’ve had a real use for JNDI. All of our resources are application managed as opposed to configured in a application server. We avoid depending on the app server whenever possible, as those dependencies make testing difficult or lead to duplicate configuration. That’s not to say it isn’t a valid solution, it’s just one that doesn’t work for us because of the way we do things.

  5. rajiv says:

    Hi,
    I have specified all the three locations as,

    classpath:application.properties
    classpath:local.properties
    ${config}

    and in my tomcat run configuration I have specified a VM argument as -Dconfig=”C:user.properties”
    which looks fine to me but when I run tomcat it complains about file not found C:user.properties, Can anyone tell me if am missing anything in here.

  6. christian says:

    Rajiv:

    Try -Dconfig=file:c://user.properties instead.

    Christian

  7. Rajiv says:

    Thanks christian, that worked :) I just skipped that it takes url :) thanks again for that quick response

  8. cristian says:

    SeƱores, necesito configurar class=”org.springframework.beans.factory.config.PropertyPlaceholderConfigurer”
    para correr mi aplicacion en WAS.

    necesito saber donde configurar el classpath.

  9. Lakshman says:

    Hi,

    I have the below bean definition in my application context xml file

    ${app.mode}

    “env” is a boolean in Mode class, and here is how I defined the PropertyPlaceholderConfigurer

    file:${conf.dir}/server.properties

    and in my class I am using

    System.setProperty(“JBOSS.conf.dir”, C:/jboss/server/default/conf);
    applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);

    Now this {value} is not getting resolved while running the applicaiton. I am getting the below error

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘mode’ defined in class path resource [applicationContext.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyAccessExceptionsException: PropertyAccessExceptionsException (1 errors); nested propertyAccessExceptions are: [org.springframework.beans.TypeMismatchException: Failed to convert property value of type to required type [boolean] for property ‘env'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [${app.mode}]]
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1031)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:823)
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:345)
    org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:226)
    org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:147)
    org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:275)
    org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:318)
    org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:81)
    org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:66)
    org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:57)

    Please suggest how to proceed

  10. sega says:

    Good article, adding it to my bookmarks!

  11. sak says:

    This is a nice overview. The only drawback in all of above examples is that they silently ignore any potential configuration issues. It is often a good idea for the application to complain by throwing an error at start up time in the any of the following cases:

    * Properties resource file does not exist
    * A required property could not be resolved

    I think by always “falling back” to some default setting raises the possibility of mis-configuration. One could be thinking we have a successful deployment on staging when it could be pointing to some other environment like QA …

    Any ideas on how to have strict failure feedback along with above flexibility?

  12. reno says:

    Nice article, but with some fallacious info! I mean in #4 configuring a PPC with another one simply wont work! They will be instantiated first, and will be working on BeanDefinition-s, perfectly replacing placeholders (in PPC definitions as well), but PPCs won’t be instantiated again! See details: http://forum.springsource.org/showthread.php?t=60415.
    It took me almost two days to understand why it does not work…:-(

  13. zeeman says:

    Thans for this summary!

    It helped me a lot :)

  14. CMS has always been the way to go!

  15. Neeraj says:

    Hi,

    I have a requirement to se log file path in log4j.properties at runtime.
    log4j.appender.GeneralFileAppender.File=${LOG_DIR}General.log

    i setup LOG_DIR as env varible in weblogic container. In simple java program i am able to get env variable value. but in spring 2.5 , it is not able to pick up this varible.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>