Efficiently manage dependencies thanks to maven  BOM
Tags: todayilearned, spring
Published: 2020-08-02 20:13:44

## Situation

Imagine having several applications using maven for dependency management. You have to make sure all these applications remain consistent in their dependency versions.
For instance, if these applications use a shared database server like mongo db, you want to be sure they are all up to date with mongo db API or they will break.
Updating all these applications can take a lot of your time, also you may have many other dependencies to maintain: security, plugins etc...

## Solution: Use a Maven BOM

BOM stands for Bills Of Materials. A BOM gather multiple versions of dependencies and can be imported in your application POM.
BOM dependencies:
 -  will override all the transient dependencies versions of your project.
 - will be overriden by direct dependencies.

Let's take an example with dependencies of one of our CLI app:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.jbebar</groupId>
    <artifactId>my-cli-app</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongodb-driver-sync</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>info.picocli</groupId>
            <artifactId>picocli</artifactId>
            <version>4.4.0</version>
        </dependency>
    </dependencies>
</project>
```

Let's create a BOM to manage these two dependencies, and also spring jdbc as it is used in other of our CLI apps and we need to share this BOM:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.jbebar</groupId>
    <artifactId>sample-BOM</artifactId>
    <version>1.0.0</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.mongodb</groupId>
                <artifactId>mongodb-driver-sync</artifactId>
                <version>4.1.0</version>
            </dependency>
            <dependency>
                <groupId>info.picocli</groupId>
                <artifactId>picocli</artifactId>
                <version>4.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.8.RELEASE</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>
```

Let's now add this BOM in dependency management section of our app with the type `pom` and the scope `import` and remove the versions of our dependencies.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.jbebar</groupId>
    <artifactId>my-cli-app</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jbebar</groupId>
                <artifactId>sample-BOM</artifactId>
                <version>1.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongodb-driver-sync</artifactId>
        </dependency>
        <dependency>
            <groupId>info.picocli</groupId>
            <artifactId>picocli</artifactId>
        </dependency>
    </dependencies>
</project>
```

We notice our versions are the same has in the maven BOM we created after running `mvn dependency:tree` or checking in our IDE. The BOM settles the dependencies versions for us.

Also, if we change the version of Picocli to 3.3.0 in the project POM, maven will prioritize the direct dependencies versions and not take the BOM version of picocli into account, we can run a `mvn dependency:tree` to see this:

[INFO] org.jbebar:my-cli-app:jar:1.0-SNAPSHOT
[INFO] +- org.mongodb:mongodb-driver-sync:jar:4.1.0:compile
[INFO] |  +- org.mongodb:bson:jar:4.1.0:compile
[INFO] |  \- org.mongodb:mongodb-driver-core:jar:4.1.0:compile
[INFO] \- info.picocli:picocli:jar:3.3.0:compile

Moreover, the jdbc dependency is not present. That is why it is common to see BOM's with dozens of dependencies. The spring boot BOM is a good example.

## Example of commonly used BOM: the spring boot BOM

To see the list of dependencies managed by spring boot you can have a look [here](https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html) It manages spring dependencies as well as other dependencies (ex: kotlin coroutines, mongo db driver).

Another good reason to use this BOM is to avoid inconsistency between spring dependencies.
As said at the beginning, the BOM overrides transient dependencies.
Two springs dependencies can be present as two transient dependencies, for instance it can come from dependency A and dependency E:

> A -> B -> C:2.0.0

> E -> C:1.0.1

In that case, C will be in version C:1.0.1. This makes it hard to know which dependency is chosen.
Above all it can lead to unexpected issues, what happens if dependency C is shared and needs to be at least in 2.0.0 for a given dependency but is set at 1.0.0 by a more direct dependency?
Using the spring boot BOM prevents from these issues between spring dependencies as it overrides all the transient versions.

Thanks for reading, any suggestions are welcome :).