In this last section, we'll speak about how to refactor POMs of multimodule projects in order to avoid errors and dependency conflicts.
When we have a project consisting of several modules, we will sometimes want to build only a subset of them. If we build the parent project, all the modules will be compiled. On the other hand, building each module separately can be tedious, and we should remember to build the modules in the right order if they depend on each other. To accomplish all these needs, we can use an additional aggregate POM. Let's consider our transportation project example again and suppose that we want to clean and build not only the transportation-acq-ear
module but also all the other modules on which it depends. We can create the following transportation-acq-pom.xml
file in the project root directory (at the same level of the parent POM):
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.packt.examples</groupId> <artifactId>transportation-acq</artifactId> <version>1.0</version> <packaging>pom</packaging> <modules> <module>transportation-acq-ear</module> <module>transportation-acq-war</module> <module>transportation-acq-ejb</module> </modules> </project>
As Maven uses the pom.xml
file present in the project directory by default, we can build the aggregate POM instead of the parent POM using the –f
parameter, as follows:
$ mvn –f transportation-acq-pom.xml clean install [...] [INFO] Reactor Build Order: [INFO] [INFO] transportation-acq-ejb [INFO] transportation-acq-war [INFO] transportation-acq-ear [INFO] transportation-acq [...]
As we can see, the Maven Build Reactor builds the modules taking account of the dependencies among them, and so it first builds the EJB module, followed by the WAR module, and finally the EAR module.
We can obtain the same result directly from the command line of the project directory, as follows:
$ mvn -pl transportation-acq-ear -am install
The –pl
parameter allows us to specify a list of modules to build, and the –am
parameter tells Maven to build the projects required by the list. Without the –am
(or --also-make
) parameter, only the modules of the list (in this case only the EAR module) will be built.
The Maven dependency mechanism can prove to be a double-edged weapon, especially in multimodule projects, or in case of conflicts between dependencies. Of course, we are speaking of conflicts regarding different versions or scopes of dependencies having the same groupId
and artifactId
. In these cases, the default Maven behavior is as follows:
If we want to assure that all the modules of a project use the same dependency versions of certain artifacts even when they are transitive dependencies, we have to use a <dependencyManagement>
element in our POM. In the case of multimodule projects, the dependency management configuration is usually specified in the parent POM.
For example, in the parent POM of our transportation project, we can insert an element such as this:
<project> [...] <dependencyManagement> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.1</version> </dependency> […] </dependencies> </dependencyManagement> [...]
This means that the specified dependencies, when declared directly or assigned as transitive dependencies, will have the default versions and scopes defined in the Dependency management section. So, we don't need to explicitly declare the versions and scopes of these dependencies, for example, we can (or better, we should) declare the SLF4J and Java EE web API dependencies simply, as follows:
<dependencies> [...] <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> </dependency> [...]
If we declare versions and scopes in the <dependencies>
element for dependencies that are declared in the dependency management section of the same POM (or of its parent POM), the values specified in the <dependencies>
element will override those specified in the <dependencyManagement>
element. On the other hand, versions and scopes of transitive dependencies will be always overridden by the values specified in the dependency management section.
Analogous to the dependency management configuration is the plugin management configuration. We can use a <pluginManagement>
element under the <build>
element to fix the plugin versions. For example, we can introduce the following element in a parent POM:
<build> <pluginManagement> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>jaxb2-maven-plugin</artifactId> <version>1.6</version> </plugin> </plugins> </pluginManagement>
This will help us fix the version of the JAXB-2 Maven Plugin across all the modules that need its declaration.
Other than plugin versions, we can also specify default plugin configurations and executions in the <pluginManagement>
section, but we discourage this practice because it might make it difficult to customize configurations and executions, which might be different for different modules of the project.