Clean and organize your pom.xml
Dependency mechanism is a core feature of Maven and is very powerful and flexible. And because of Transitive Dependencies included in version 2.0 dependency management becomes easier. But still, when we are talking about managing dependencies, things can be done in two ways: easy/fast one and the right one. In this tutorial I will try to show you the second one.
What is a POM?
A Project Object Model or POM is an XML file that contains project configuration used by Maven to build it. The minimal POM should include tags that describe project: project root, modelVersion, groupId, artifactId and version. These last three values form the project artifact name, in format ::.
One of the configurations that can be included in the POM is the project dependencies that are based on artifacts. In our projects we are using them by including with all parts of the artifact name, in POM’s section .
Why we should organize dependencies?
Dependencies can be included in the project in many different ways. Because all approaches can be combined with each other we can simply lose control of it and finish with mixed artifacts versions, often not compatible with each other. This is not a situation we want to find ourselves in when some features stop working because of not compatible classes, etc. In next sections we will describe better ways how to organize this chaos.
Managing dependencies
POMs can be arranged in structures when one POM can inherit configuration from his parent. One parent POM can have many children POMs. Each child should have his parent declared or parent POM should include list of children. Any child can also have his own children, and each of the children will be inheriting configuration not only from the direct parent but also from his parent’s parent, etc. Because we have special section , this inherits mechanism can be used to manage our dependencies. All we need to do is to declare this section in our top parent POM and specify all dependencies that we want to use in whole project, ex.:
<dependencymanagement>
<dependencies>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-core</artifactid>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencymanagement>
and in our sub POMs we declare only that we want to use this dependency:
<dependencies>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-core</artifactid>
</dependency>
</dependencies>
without specifying version or scope. It allows us to easily handle between artifacts versions, because we need to check only one file to change it for all places.
External parent POM
Instead of creating own parent POM with dependecyManagement section we can use existing one. Good example here is Spring Boot. To use it simply declare parent:
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version>2.2.2.RELEASE</version>
</parent>
In this way beside dependencies we are also getting shared plugins configuration. Sure, we can do it by ourselves by using pluginManagement in similar way how we used dependencyManagement, but instead we can use ready solution.
BOM
BOM is a shortcut from Bill Of Materials. It’s a special kind of POM and another way how to configure dependencies, ie. when we cannot use parent POMs. To add it to your project you can set is as parent POM, but also you can import it in dependencyManagement section:
<dependencymanagement>
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-dependencies</artifactid>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencymanagement>
Cleaning
Now, if you know how to manage your dependencies let’s see how to clean your project.
Properties
If you are using similar dependencies in same version, like:
<dependency>
<groupid>org.apache.activemq</groupid>
<artifactid>activemq-broker</artifactid>
<version>5.15.11</version>
</dependency>
<dependency>
<groupid>org.apache.activemq</groupid>
<artifactid>activemq-camel</artifactid>
<version>5.15.11</version>
</dependency>
you can use
<dependency>
<groupid>org.apache.activemq</groupid>
<artifactid>activemq-broker</artifactid>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupid>org.apache.activemq</groupid>
<artifactid>activemq-camel</artifactid>
<version>${activemq.version}</version>
</dependency>
and declare version in properties:
<properties>
<activemq.version>5.15.11</activemq.version>
</properties>
This approach will allow you not to forget to change any version, when upgrading.
Dependencies duplicates
If you have many POMs in your project is easy to got duplicate declarations. To quickly find duplicates you can use maven-enforcer-plugin. Just add:
<build>
<plugins>
<plugin>
<groupid>org.apache.maven.plugins</groupid>
<artifactid>maven-enforcer-plugin</artifactid>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>no-duplicate-declared-dependencies</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<banduplicatepomdependencyversions>
</banduplicatepomdependencyversions></rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
in your project and after each project build you will see warnings if you have duplicates:
[WARNING] Some problems were encountered while building the effective model for pl.j-labs:spring-boot-demo:jar:1.0.0-SNAPSHOT
[WARNING] 'dependencyManagement.dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique: org.springframework.boot:spring-boot-starter:jar -> duplicate declaration of version 2.2.2.RELEASE @ line 33, column 29
[WARNING]
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING]
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
Transitive dependencies
Another good example why to use maven-enforcer-plugin is to eliminate transitive dependencies from your project. Declaring dependencies directly can be useful, because it simplifies detecting conflicts or makes us sure that we use exactly what we want.
To obtain list of dependencies not directly declared we should add another rule to our plugin configuration:
<rules>
<bantransitivedependencies>
[...]
</bantransitivedependencies></rules>
In this case build will fail when plugin will detect transitive dependencies:
[WARNING] Rule 0: org.apache.maven.plugins.enforcer.BanTransitiveDependencies failed with message:
Rule 0: org.apache.maven.plugins.enforcer.BanTransitiveDependencies failed with message:
pl.j-labs:spring-boot-demo:jar:1.0.0-SNAPSHOT
org.springframework.boot:spring-boot-starter-web:jar:2.2.2.RELEASE:compile has transitive dependencies:
org.springframework.boot:spring-boot-starter:jar:2.2.2.RELEASE:compile
org.springframework.boot:spring-boot:jar:2.2.2.RELEASE:compile
org.springframework.boot:spring-boot-autoconfigure:jar:2.2.2.RELEASE:compile
org.springframework.boot:spring-boot-starter-logging:jar:2.2.2.RELEASE:compile
ch.qos.logback:logback-classic:jar:1.2.3:compile
ch.qos.logback:logback-core:jar:1.2.3:compile
org.apache.logging.log4j:log4j-to-slf4j:jar:2.12.1:compile
org.apache.logging.log4j:log4j-api:jar:2.12.1:compile
org.slf4j:jul-to-slf4j:jar:1.7.29:compile
jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
org.yaml:snakeyaml:jar:1.25:runtime
org.springframework.boot:spring-boot-starter-json:jar:2.2.2.RELEASE:compile
[...]
In addition to the two functions I have described, this plugin has many more uses Full functionality list can be found on his page.
Conclusion
Sometimes complications resulting from clutter in our POM files can be very painful. A clean POM will not only avoid them but also speed up future application development.