Maven sourcing dependency version from great-grand-parent instead of our parents dependency-management
Asked Answered
L

4

6

Below shows the hierarchy of my POMs.

You can see that we have a company parent-pom for spring boot projects. This POM has spring-boot-starter as it's parent, and it imports our own dependency-management BOM.

[INFO] --- hierarchy-maven-plugin:1.4:tree (default-cli) @ user-service ---
[INFO]  PARENT com.MY_COMPANY.platform:user:3.20.14-SNAPSHOT
[INFO]    PARENT com.MY_COMPANY.platform:spring-boot-parent:3.20.12-SNAPSHOT
[INFO]      PARENT org.springframework.boot:spring-boot-starter-parent:1.5.12.RELEASE
[INFO]        PARENT org.springframework.boot:spring-boot-dependencies:1.5.12.RELEASE  <<<< This pom defines assertJ 2.x
[INFO]          [ other imports ]
[INFO]      IMPORT com.MY_COMPANY:dependencyManagementBase:2.23.14-SNAPSHOT     <<<<<<<<<<<< This pom defines assertJ 3.x
[INFO]    IMPORT com.MY_COMPANY.platform:platform-dependency-management:1.20.7
[INFO] ------------------------------------------------------------------------

To focus on a specific, we define AssertJ 3 in our dependency-management; however, spring-boot-dependencies defines AssertJ 2. Not a big deal with assertJ, but there are other fish like Mongo-Java-Driver that are not picking up our version.

How does Maven choose precedence here? Why doesn't our dependency-management win over a far-ancestor's dependency-management?

I also noticed that if I add AssertJ as a dependency of MY_COMPANY.platform:spring-boot-parent, it also does NOT use the version in our dependency-management (so I'll just keep it there for now, so the hierarchy under microscope is shorter).

EDIT - Add abbreviated POMs

com.MY_COMPANY.platform:spring-boot-parent

<?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>com.MYCOMPANY.platform</groupId>
    <artifactId>spring-boot-parent</artifactId>
    <version>3.20.12-SNAPSHOT</version>
    <packaging>pom</packaging>

    <prerequisites>
        <maven>3.0.4</maven>
    </prerequisites>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.12.RELEASE</version>
    </parent>

    <properties>
        <MYCOMPANYdependencymanagement.version>2.23.13</MYCOMPANYdependencymanagement.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.MYCOMPANY</groupId>
                <artifactId>dependencyManagementBase</artifactId>
                <version>${MYCOMPANYdependencymanagement.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

com.MY_COMPANY:dependencyManagementBase

<?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>com.MYCOMPANY</groupId>
    <artifactId>dependencyManagementBase</artifactId>
    <version>2.23.13</version>
    <packaging>pom</packaging>

    <modules>
        <module>spring-dep-man</module>
    </modules>

    <properties>
        <org.assertj-core.version>3.5.2</org.assertj-core.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.assertj</groupId>
                <artifactId>assertj-core</artifactId>
                <version>${org.assertj-core.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

**EDIT 2 - Add verbose hierarchy showing different versions **

~/p/springbootparentpom> mvn hierarchy:tree -Dlevel=full
[INFO] --- hierarchy-maven-plugin:1.4:tree (default-cli) @ spring-boot-parent ---
[INFO] Displaying hierarchy.
[INFO]  PARENT org.springframework.boot:spring-boot-starter-parent:1.5.12.RELEASE
[INFO]    PARENT org.springframework.boot:spring-boot-dependencies:1.5.12.RELEASE
[INFO]          DEP_MANAGEMENT ........
[INFO]          DEP_MANAGEMENT org.assertj:assertj-core:2.6.0
[INFO]          [ ... Many DEP_MAN and IMPORT ... ]
[INFO]  IMPORT com.MYCOMPANY:dependencyManagementBase:2.23.14-SNAPSHOT
[INFO]        DEP_MANAGEMENT ........
[INFO]        DEP_MANAGEMENT org.assertj:assertj-core:3.5.2
[INFO]        DEP_MANAGEMENT ........
Lyublin answered 4/5, 2018 at 22:42 Comment(4)
I'm having the same issue - I hope someone knows the answerAbatis
Can you post the dependency tag for assertj in your POM?Raynaraynah
I would actually like to see you dependencyManagement and parent also, if at all possible.Raynaraynah
Updated with POMs.Lyublin
R
1

My guess is that it is grabbing the nearest.

Dependency mediation - this determines what version of a dependency will be used when multiple versions of an artifact are encountered. Currently, Maven 2.0 only supports using the "nearest definition" which means that it will use the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project's POM. Note that if two dependency versions are at the same depth in the dependency tree, until Maven 2.0.8 it was not defined which one would win, but since Maven 2.0.9 it's the order in the declaration that counts: the first declaration wins.

What I do for stuff like this is go to my IDE's dependencies section for a given project. It shows me all dependencies, what version, and where it came from (see bottom of pop-up on attached image below for an example).

enter image description here

Raynaraynah answered 4/5, 2018 at 23:24 Comment(1)
I use IntelliJ. It shows a up-arrow icon next to the dependency. When I hover, it shows the declaration snippet form my dep-man, and when I click the icon it takes me to the dependency-management that I want to use! So it seems intelliJ figures it out correctly, but not maven command-line.Lyublin
P
1

The issue is that your project is relying not on a parent for dependency management, but on an imported project. The Maven dependency hierarchy doesn't work that way. Probably the best solution would be to move the 'parent spring-boot-starter-parent' declaration into the MY_COMPANY:dependencyManagementBase project. Then change the parent declaration in com.MYCOMPANY.platform to point at your dependencyManagementBase project. That way you would have a clear inheritance hierarchy.

What you currently have does not really use dependency management. i.e. if you were to change the 'dependecyManagement' section in the dependencyManagementBase project to 'dependency', you would get the same results.

Pantry answered 8/5, 2018 at 20:47 Comment(2)
Thanks. While you posted this I was posting another post with my findings (https://mcmap.net/q/1799152/-maven-sourcing-dependency-version-from-great-grand-parent-instead-of-our-parents-dependency-management). Can you confirm we are saying about the same thing? Though it kinda sounds like what you're saying is you CAN'T import dependency management?Lyublin
Exactly. You can't import dependency management and expect it to change a version that was defined in a higher level pom. You either need to declare the version in your lower level project, or make your dependencyManagement project the parent.Pantry
L
1

Doesn't seem like anyone has the answer. So I'll just answer with my findings.

Given OP doesn't work. My next strategy is to IMPORT the spring-boot-starter-parent and spring-boot-dependency POMs, since I can't use them as parents.

This however has it's own behaviors.

I found that if my dependency management looks like this, my versions win:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.mycompany</groupId>
            <artifactId>dependency-management-slim</artifactId>
            <version>2.23.14-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

However, I decided I wanted to separate my custom code from my spring-boot mimicking pom. So in order to preserve priority I figured I had to do this:

service-root -> only import my-dependency-management
^-- spring-boot-parent -> mimick and import spring-boot-starter-parent
^-- service-parent -> has our common dependencies and profiles
^-- service-impl - code

However, that did NOT work; spring-boot versions overrode our custom versions. So I had to do this:

root-parent -> nothing
^-- spring-boot-parent -> mimick and import spring-boot-starter-parent
^-- service-parent -> import my-dependency-management
^-- service-impl - code

Conclusions

  • IF importing multiple dependency-management in the SAME pom, then the first management-versions win.
  • IF you are importing in multiple poms, the last one imported wins.
  • IF a parent defines has dependency-management with individual dependencies defined, you cannot override them with dependency management (This is original problem).

¯\_(ツ)_/¯

Lyublin answered 8/5, 2018 at 20:52 Comment(0)
P
0

Imported dependencyManagement always looses against directly defined dependencyManagement, regardless from which level or the hierarchy.

I would avoid the import and instead set the properties that define the versions in the spring boot starter parent.

Pod answered 21/3, 2023 at 12:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.