Traditionally, a structure of a set of Maven projects is rather treelike. With a direct symmetrical module parent relation. Also, the parent POM then tends to hold many dependencies which are often only relevant to a subset of modules. This approach is outdated and tends to be a maintenance and development nightmare. Especially when more than one person develops on it.

These monolithic builds suffer from a variety of problems. First and foremost, releasing any individual module is impossible and releasing the tree as a whole is tedious at best. Since this can be a complex operation, every POM has to be in tip-top shape. In my experience, this is rarely the case on any project that actually sees any significant development. Secondly, you can’t just build ‘part’ of the project, you have to build the entire structure. Also, if a dependency is changed, especially if under dependency management, this will trigger a rebuild of the entire project. If on the other hand, dependency management is not used, the project as a whole suffers as there is no clear way to make sure all parts use the same dependencies. Also, targeting different run times generally meant a lot of dependency cherry picking and copy pasting for various profiles.

First evolution

One of the first evolutionary steps in Maven project management was the addition of the Parent POM. This Parent POM without modules would specify the bare basics of all the Modules. Setting out some basic plug-in configuration as well as other standard information such as, SCM, CI, team information and repository configuration. This solved the most important problem, monolithic builds. Easily broken builds and frustrated release moments. If done properly, build structures aren’t even deployed to begin with.

What followed next didn’t take all too long, namely dependency POMs. Basically, POMs that grouped dependencies together to provide a fixed set dependencies commonly used. For example, we had a set called “common” which included various Apache Commons projects. Then there was “test” which included things such as EasyMock and JUnit. Other variations aren’t hard to imagine but they included things such as “jcr”, “webapp”, “jpa”, “jaxrs-server” and “jaxrs-client”. Versions were fixed for the entire dependency subtree by making this dependency subtree a single build. Properties could then be used to pin versions for all dependency sets. This well-defined set of dependencies eliminated a lot of the copy paste work.

It didn’t take long for the folks at Maven to give us some extra options with this setup, namely by including the “import” scope for dependency management. This allowed dependency POMs to become true meta-projects. For example, if you had a “webapp” dependency for JSF projects, but you wanted to keep the option to include something like PrettyFaces without including it everywhere. You’d be able to set PrettyFaces in dependency management of “webapp”. Then importing the “webapp” dependency in the dependency management of the project you wanted to use it in, and including PrettyFaces as dependency there, without specifying a version. The same holds for example for “jpa” and the “gis” provider du jour.

Recent experiments

More recently, I’ve had to work on projects that had to work with multiple deployment types. Namely a simple Servlet Container and a heavier JEE Container. We used CDI as DI framework on both deployments. This worked fine with the JEE container but required some work with the Servlet Container. We programmed strictly against the API, this meant we could use any provider, theoretically. But we needed a new kind of meta-dependency for the various deployments. We solved this by creating a runtime meta-dependency, let's call these container dependencies. One such container dependency specified which dependencies were provided and which had to be added.

We then added profiles to the applications that required this, to use these container dependencies as dependencies and as dependency management. As such adding some dependencies and setting others to provided. We also had some shared dependencies we had to add to a container. We configured the dependency plugin to gather these for us. These together ensured we were able to build everything properly, slim down our packages while providing a large common ground for all modules.

We’ve seen various ways of working that incrementally tweaked how we look at Maven project management. These all tend to add minor improvements that make working with Maven a breeze, both for development, testing and deployment. Personally, I’m very happy with this way of working, but I’m looking forward to what we come up next to make our lives easier.