In this article I’m going to present a simple configuration solution for small to medium-sized applications running in Tomcat. It uses only the configuration mechanism that Java and Tomcat provide out-of-the-box, requires minimal code and supports multiple installations.
Most applications need some sort of environment-specific configuration. Depending on the environment (Test, Prod, Customer-specific…) you usually have different databases, use different API endpoints or different accounts for accessing a resource. These kind of things should not be hard-coded, rather your code should retrieve these values from a configuration, so that the same code can be run in different environments using different configurations.
When I implemented a configuration solution for my current project, I expected that the number of configuration parameters would be fairly small, so it seemed inappropriate to add a lot of complexity to the project just for that. Instead I wanted to keep things simple and avoid adding libraries or adding complexity to the build process. In this article I’m going to show you what I came up with, using just the facilities that were already provided by Java and my application server.
Tomcat provides a standard configuration mechanism that uses JNDI to locate application resources. JNDI is basically an API that matches names to objects. It does so transparently, without the application knowing where these objects come from. Resources can be anything from primitives like
Boolean, up to complete
DataSource instances for accessing your database. It's worth noting that locating resources through JNDI is also supported by other Java application servers, so your configuration code might work on other application servers with minimal changes. However how these resources are defined (format, file location) is specific to each server.
Specifically in Tomcat, resources are defined in
context.xml files. Here is an example configuration file with some basic parameters and a data source:
Yes, it uses XML. And it’s quite verbose at that! However as unpopular as XML has become lately, this is completely adequate for a small configuration. And it’s supported out-of-the-box by Tomcat, so we don’t need to add further libraries or code to use it. Just by using the
Resource tags we can cover about most configuration needs I have seen for smaller business applications. Have a look at the docs for more information on the structure and content of these files.
Speaking of out-of-the-box, we do not need anything special to access the configuration. We can just use the JNDI classes from the
javax.naming package which are part of the J2SE spec. The code below demonstrates how to access the resources defined in the example configuration from above:
The API is quite simple and automatically provides the configuration values as the correct Java type. You could even instantiate custom Java Beans using this mechanism. To use this code in a configuration class you would need to add exception handling and possibly log missing configuration values.
Handling multiple environments
I still haven’t told you where to put the
context.xml file yet. There are multiple options:
example.war/META-INF/context.xml- Directly packaged into the web application archive.
tomcat/conf/context.xml- Globally defined in the Tomcat installation's config folder.
However, it’s rare that you have only one installation of an application. Usually there are multiple installations, possibly even on the same server. For example, in my project I need to maintain the following ones:
- Development machine
- Test installation
- Production installation
With that it is not practical to store the configuration in the application archive, because then all installations use the same configuration. Also a global configuration in Tomcat might not work if there are multiple installations on the same server. What I have often seen is a solution where you generate one artefact (WAR) for each environment by using Maven build profiles. Using the Tomcat configuration approach from before, each profile would package a different
context.xml into the resulting .war file. However this would increase the complexity of the build, something I wanted to avoid.
Instead I was looking for a solution that allowed me to configure application-specific
context.xml files directly in Tomcat. As it turns out there is a way by using a special naming convention (docs):
engine-name- Name of the Tomcat engine that your application runs on. This is
host-name- Host name that is configured in Tomcat. This is
context-name- Context name used by your application, that is the part of the URL after the host name. If you are using the default WAR auto-deployment in Tomcat then this would be the name of your .war file.
To give an example: if we wanted to deploy three installations of an application for test, stage and production, we would create three
And then deploy the same .war file three times as:
And each installation would pick up its specific configuration automatically.
With this we have a simple yet powerful configuration mechanism for web applications deployed in Tomcat. Let’s summarise the advantages and disadvantages here.
- Configuration is simple to write and simple to access in Java
- Works out-of-the-box, no libraries
- Configuration files are picked up by convention, no hard-coded paths
- Can use same build artefact in every environment
- Supports multiple installations
- Editing the XML gets complex with lots of configuration values
- Becomes hard to manage when frequently adding new configuration parameters with lots of installations
- Due to the XML format some characters like
&need to be escaped, here you need to pay attention, especially with passwords
My own experience with this mechanism has been quite good so far. Our number of configuration parameters has considerably increased during development, but it’s still manageable. Adding new parameters in two installations and on each developer machine works fine. We have a channel in our company chat where we communicate when someone adds a new parameter. We also maintain a template context.xml in our repository that contains all existing parameters.
Thanks for reading, I hope this approach is useful to other developers!