├── .gitignore
├── C-depends-on-guava-20
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── c
│ │ └── ThisClassInCDependsOnGuava20.java
└── pom.xml
├── B-depends-on-C
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── b
│ │ └── ThisClassInBDependsOnGuava20.java
└── pom.xml
├── h8_solution4-use-a-shaded-jar
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── problem
│ │ ├── H8CallingGuava10.java
│ │ └── H8CallingGuava20.java
└── pom.xml
├── h1_problem-clashing-dependencies
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── problem
│ │ └── H1CallingGuava20.java
└── pom.xml
├── h2_solution1-direct-dependency
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── problem
│ │ ├── H2CallingGuava20.java
│ │ └── H2CallingGuava10.java
└── pom.xml
├── h3_solution2-dependency-management
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── problem
│ │ ├── H3CallingGuava20.java
│ │ └── H3CallingGuava10.java
└── pom.xml
├── h5_solution3-maven-enforcer-plugin_fixed
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── problem
│ │ ├── H5CallingGuava20.java
│ │ └── H5CallingGuava10.java
└── pom.xml
├── h4_solution3-maven-enforcer-plugin_breaks-build
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── problem
│ │ ├── H4CallingGuava20.java
│ │ └── H4CallingGuava10.java
└── pom.xml
├── h6_problem-non-binary-compatible-dependencies
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── problem
│ │ ├── H6CallingGuava10.java
│ │ └── H6CallingGuava20.java
└── pom.xml
├── A-depends-on-guava-10
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── a
│ │ └── ThisClassInADependsOnGuava10.java
└── pom.xml
├── h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── topdesk
│ │ └── maven_hell
│ │ └── a
│ │ └── ThisClassDependsOnGuava10.java
├── dependency-reduced-pom.xml
└── pom.xml
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .metadata/
2 | .recommenders/
3 | *.classpath
4 | *.project
5 | *.settings/
6 | */target/
7 |
--------------------------------------------------------------------------------
/C-depends-on-guava-20/src/main/java/com/topdesk/maven_hell/c/ThisClassInCDependsOnGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.c;
2 |
3 | import com.google.common.graph.GraphBuilder;
4 |
5 | public class ThisClassInCDependsOnGuava20 {
6 | public static void methodOnlyInGuava20() {
7 | GraphBuilder.undirected().build();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/B-depends-on-C/src/main/java/com/topdesk/maven_hell/b/ThisClassInBDependsOnGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.b;
2 |
3 | import com.topdesk.maven_hell.c.ThisClassInCDependsOnGuava20;
4 |
5 | public class ThisClassInBDependsOnGuava20 {
6 | public static void methodOnlyInGuava20() {
7 | ThisClassInCDependsOnGuava20.methodOnlyInGuava20();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/h8_solution4-use-a-shaded-jar/src/main/java/com/topdesk/maven_hell/problem/H8CallingGuava10.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.a.ThisClassDependsOnGuava10;
4 |
5 | public class H8CallingGuava10 {
6 | public static void main(String[] args) {
7 | ThisClassDependsOnGuava10.methodOnlyInGuava10();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h1_problem-clashing-dependencies/src/main/java/com/topdesk/maven_hell/problem/H1CallingGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.b.ThisClassInBDependsOnGuava20;
4 |
5 | public class H1CallingGuava20 {
6 | public static void main(String[] args) {
7 | ThisClassInBDependsOnGuava20.methodOnlyInGuava20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h2_solution1-direct-dependency/src/main/java/com/topdesk/maven_hell/problem/H2CallingGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.b.ThisClassInBDependsOnGuava20;
4 |
5 | public class H2CallingGuava20 {
6 | public static void main(String[] args) {
7 | ThisClassInBDependsOnGuava20.methodOnlyInGuava20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h8_solution4-use-a-shaded-jar/src/main/java/com/topdesk/maven_hell/problem/H8CallingGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.b.ThisClassInBDependsOnGuava20;
4 |
5 | public class H8CallingGuava20 {
6 | public static void main(String[] args) {
7 | ThisClassInBDependsOnGuava20.methodOnlyInGuava20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h2_solution1-direct-dependency/src/main/java/com/topdesk/maven_hell/problem/H2CallingGuava10.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.a.ThisClassInADependsOnGuava10;
4 |
5 | public class H2CallingGuava10 {
6 | public static void main(String[] args) {
7 | ThisClassInADependsOnGuava10.methodBothInGuava10And20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h3_solution2-dependency-management/src/main/java/com/topdesk/maven_hell/problem/H3CallingGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.b.ThisClassInBDependsOnGuava20;
4 |
5 | public class H3CallingGuava20 {
6 | public static void main(String[] args) {
7 | ThisClassInBDependsOnGuava20.methodOnlyInGuava20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h3_solution2-dependency-management/src/main/java/com/topdesk/maven_hell/problem/H3CallingGuava10.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.a.ThisClassInADependsOnGuava10;
4 |
5 | public class H3CallingGuava10 {
6 | public static void main(String[] args) {
7 | ThisClassInADependsOnGuava10.methodBothInGuava10And20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h5_solution3-maven-enforcer-plugin_fixed/src/main/java/com/topdesk/maven_hell/problem/H5CallingGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.b.ThisClassInBDependsOnGuava20;
4 |
5 | public class H5CallingGuava20 {
6 | public static void main(String[] args) {
7 | ThisClassInBDependsOnGuava20.methodOnlyInGuava20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h4_solution3-maven-enforcer-plugin_breaks-build/src/main/java/com/topdesk/maven_hell/problem/H4CallingGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.b.ThisClassInBDependsOnGuava20;
4 |
5 | public class H4CallingGuava20 {
6 | public static void main(String[] args) {
7 | ThisClassInBDependsOnGuava20.methodOnlyInGuava20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h5_solution3-maven-enforcer-plugin_fixed/src/main/java/com/topdesk/maven_hell/problem/H5CallingGuava10.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.a.ThisClassInADependsOnGuava10;
4 |
5 | public class H5CallingGuava10 {
6 | public static void main(String[] args) {
7 | ThisClassInADependsOnGuava10.methodBothInGuava10And20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h6_problem-non-binary-compatible-dependencies/src/main/java/com/topdesk/maven_hell/problem/H6CallingGuava10.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.a.ThisClassInADependsOnGuava10;
4 |
5 | public class H6CallingGuava10 {
6 | public static void main(String[] args) {
7 | ThisClassInADependsOnGuava10.methodOnlyInGuava10();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h6_problem-non-binary-compatible-dependencies/src/main/java/com/topdesk/maven_hell/problem/H6CallingGuava20.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.b.ThisClassInBDependsOnGuava20;
4 |
5 | public class H6CallingGuava20 {
6 | public static void main(String[] args) {
7 | ThisClassInBDependsOnGuava20.methodOnlyInGuava20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/h4_solution3-maven-enforcer-plugin_breaks-build/src/main/java/com/topdesk/maven_hell/problem/H4CallingGuava10.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.problem;
2 |
3 | import com.topdesk.maven_hell.a.ThisClassInADependsOnGuava10;
4 |
5 | public class H4CallingGuava10 {
6 | public static void main(String[] args) {
7 | ThisClassInADependsOnGuava10.methodBothInGuava10And20();
8 | System.out.println("Done");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/A-depends-on-guava-10/src/main/java/com/topdesk/maven_hell/a/ThisClassInADependsOnGuava10.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.a;
2 |
3 | import com.google.common.base.Equivalences;
4 | import com.google.common.collect.ImmutableMap;
5 |
6 | public class ThisClassInADependsOnGuava10 {
7 | public static void methodOnlyInGuava10() {
8 | Equivalences.identity();
9 | }
10 |
11 | public static void methodBothInGuava10And20() {
12 | ImmutableMap.of();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10/src/main/java/com/topdesk/maven_hell/a/ThisClassDependsOnGuava10.java:
--------------------------------------------------------------------------------
1 | package com.topdesk.maven_hell.a;
2 |
3 | import com.google.common.base.Equivalences;
4 | import com.google.common.collect.ImmutableMap;
5 |
6 | public class ThisClassDependsOnGuava10 {
7 | public static void methodOnlyInGuava10() {
8 | Equivalences.identity();
9 | }
10 |
11 | public static void methodBothInGuava10And20() {
12 | ImmutableMap.of();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/A-depends-on-guava-10/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.topdesk.maven-hell
5 | A-depends-on-guava-10
6 | 0.0.1-SNAPSHOT
7 | A
8 |
9 | com.topdesk
10 | open-source-parent
11 | 1.0.0
12 |
13 |
14 |
15 | com.google.guava
16 | guava
17 | 10.0.1
18 |
19 |
20 |
--------------------------------------------------------------------------------
/C-depends-on-guava-20/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | C-depends-on-guava-20
11 | 0.0.1-SNAPSHOT
12 | C
13 |
14 |
15 | com.google.guava
16 | guava
17 | 20.0
18 |
19 |
20 |
--------------------------------------------------------------------------------
/B-depends-on-C/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | B-depends-on-C
11 | 0.0.1-SNAPSHOT
12 | B
13 |
14 |
15 | com.topdesk.maven-hell
16 | C-depends-on-guava-20
17 | 0.0.1-SNAPSHOT
18 |
19 |
20 |
--------------------------------------------------------------------------------
/h1_problem-clashing-dependencies/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | h1_problem-clashing-dependencies
11 | 0.0.1-SNAPSHOT
12 |
13 |
14 | com.topdesk.maven-hell
15 | A-depends-on-guava-10
16 | 0.0.1-SNAPSHOT
17 |
18 |
19 | com.topdesk.maven-hell
20 | B-depends-on-C
21 | 0.0.1-SNAPSHOT
22 |
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Joep Weijers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/h2_solution1-direct-dependency/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | h2_solution1-direct-dependency
11 | 0.0.1-SNAPSHOT
12 |
13 |
14 | com.topdesk.maven-hell
15 | A-depends-on-guava-10
16 | 0.0.1-SNAPSHOT
17 |
18 |
19 | com.topdesk.maven-hell
20 | B-depends-on-C
21 | 0.0.1-SNAPSHOT
22 |
23 |
24 |
25 | com.google.guava
26 | guava
27 | 20.0
28 |
29 |
30 |
--------------------------------------------------------------------------------
/h3_solution2-dependency-management/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | h3_solution2-dependency-management
11 | 0.0.1-SNAPSHOT
12 |
13 |
14 | com.topdesk.maven-hell
15 | A-depends-on-guava-10
16 | 0.0.1-SNAPSHOT
17 |
18 |
19 | com.topdesk.maven-hell
20 | B-depends-on-C
21 | 0.0.1-SNAPSHOT
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | com.google.guava
30 | guava
31 | 20.0
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/h6_problem-non-binary-compatible-dependencies/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | h6_problem-non-binary-compatible-dependencies
11 | 0.0.1-SNAPSHOT
12 |
13 |
14 | com.topdesk.maven-hell
15 | A-depends-on-guava-10
16 | 0.0.1-SNAPSHOT
17 |
18 |
19 |
20 | com.google.guava
21 | guava
22 |
23 |
24 |
25 |
26 | com.topdesk.maven-hell
27 | B-depends-on-C
28 | 0.0.1-SNAPSHOT
29 |
30 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10/dependency-reduced-pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | open-source-parent
5 | com.topdesk
6 | 1.0.0
7 | ../pom.xml/pom.xml
8 |
9 | 4.0.0
10 | com.topdesk.maven-hell
11 | h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10
12 | 0.0.1-SNAPSHOT
13 |
14 |
15 |
16 | maven-shade-plugin
17 | 3.1.0
18 |
19 |
20 | package
21 |
22 | shade
23 |
24 |
25 |
26 |
27 | com.google
28 | com.topdesk.maven-hell.shaded.com.google
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.topdesk.maven-hell
5 | h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10
6 | 0.0.1-SNAPSHOT
7 |
8 | com.topdesk
9 | open-source-parent
10 | 1.0.0
11 |
12 |
13 |
14 | com.google.guava
15 | guava
16 | 10.0.1
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | org.apache.maven.plugins
25 | maven-shade-plugin
26 | 3.1.0
27 |
28 |
29 | package
30 |
31 | shade
32 |
33 |
34 |
35 |
36 | com.google
37 | com.topdesk.maven-hell.shaded.com.google
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/h8_solution4-use-a-shaded-jar/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | h8_solution4-use-a-shaded-jar
11 | 0.0.1-SNAPSHOT
12 |
13 |
14 |
15 | com.topdesk.maven-hell
16 | h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10
17 | 0.0.1-SNAPSHOT
18 |
19 |
20 | com.topdesk.maven-hell
21 | B-depends-on-C
22 | 0.0.1-SNAPSHOT
23 |
24 |
25 |
26 |
27 |
28 |
29 | org.apache.maven.plugins
30 | maven-enforcer-plugin
31 | 1.4
32 |
33 |
34 | enforce-banned-dependencies
35 |
36 | enforce
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/h4_solution3-maven-enforcer-plugin_breaks-build/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | h4_solution3-maven-enforcer-plugin_breaks-build
11 | 0.0.1-SNAPSHOT
12 |
13 |
14 | com.topdesk.maven-hell
15 | A-depends-on-guava-10
16 | 0.0.1-SNAPSHOT
17 |
18 |
19 | com.topdesk.maven-hell
20 | B-depends-on-C
21 | 0.0.1-SNAPSHOT
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | org.apache.maven.plugins
30 | maven-enforcer-plugin
31 | 1.4
32 |
33 |
34 | enforce-banned-dependencies
35 |
36 | enforce
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/h5_solution3-maven-enforcer-plugin_fixed/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.topdesk
6 | open-source-parent
7 | 1.0.0
8 |
9 | com.topdesk.maven-hell
10 | h5_solution3-maven-enforcer-plugin_fixed
11 | 0.0.1-SNAPSHOT
12 |
13 |
14 | com.topdesk.maven-hell
15 | A-depends-on-guava-10
16 | 0.0.1-SNAPSHOT
17 |
18 |
19 | com.google.guava
20 | guava
21 |
22 |
23 |
24 |
25 | com.topdesk.maven-hell
26 | B-depends-on-C
27 | 0.0.1-SNAPSHOT
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | org.apache.maven.plugins
36 | maven-enforcer-plugin
37 | 1.4
38 |
39 |
40 | enforce-banned-dependencies
41 |
42 | enforce
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Maven Dependency Hell examples
2 | This project illustrates some of the forms of dependency clashes you may run into when using Apache Maven.
3 |
4 | ## The subject dependencies:
5 | * **Guava**: Google's well known convenience library for Java. Guava has a steady release cycle and contains breaking changes between major versions.
6 | * **Project A** depends on Guava version 10.0.1. This project calls two methods in Guava: `Equivalences.identity()` (present in Guava 10.0.1 but not in 20.0) and `ImmutableMap.of()` (present in both Guava 10.0.1 and 20.0).
7 | * **Project B** depends on Project C.
8 | * **Project C** depends on Guava version 20.0. This project calls `GraphBuilder.undirected().build()`, which is present in Guava 20.0, but not in 10.0.1.
9 |
10 | ## The first problem: Clashing Dependencies
11 | Maven's dependencies work transitively. This means that it will add all dependencies of my dependencies on the classpath. While not having to explicitly specify all dependencies is very convenient, it is also the cause of some problems which I will illustrate here. Load up this repo into your workspace and following along with the explanation.
12 |
13 | Suppose we have a project that has a dependency on both Project A and Project B, called [h1_problem-clashing-dependencies](h1_problem-clashing-dependencies/pom.xml). This project has a transitive dependency on Guava 10.0.1 through it's dependency on A. It also has a transitive dependency on Guava 20.0 through B, since B depends on C and C depends on Guava 20.0. Which version of Guava will end up at our classpath?
14 |
15 | We can determine the chosen Guava in the Dependency Hierarchy view in Eclipse. Open the `pom.xml` in an Eclipse with [M2E](https://www.eclipse.org/m2e/) installed. Alternatively we can use the `mvn dependency:tree -Dverbose` command in a shell. The output is as follows:
16 | ```
17 | [INFO] com.topdesk.maven-hell:h1_problem-clashing-dependencies:jar:0.0.1-SNAPSHOT
18 | [INFO] +- com.topdesk.maven-hell:A-depends-on-guava-10:jar:0.0.1-SNAPSHOT:compile
19 | [INFO] | \- com.google.guava:guava:jar:10.0.1:compile
20 | [INFO] | \- com.google.code.findbugs:jsr305:jar:1.3.9:compile
21 | [INFO] \- com.topdesk.maven-hell:B-depends-on-C:jar:0.0.1-SNAPSHOT:compile
22 | [INFO] \- com.topdesk.maven-hell:C-depends-on-guava-20:jar:0.0.1-SNAPSHOT:compile
23 | [INFO] \- (com.google.guava:guava:jar:20.0:compile - omitted for conflict with 10.0.1)
24 | ```
25 |
26 | We can see that Guava 20.0 is omitted for a conflict with 10.0.1. In general Maven will select the 'nearest' defined version of a dependency. In this case the path to 10.0.1 is only two steps, while Guava 20.0 is three steps. Hence 10.0.1 is selected as version.
27 |
28 | But this might not be the version we want. If we try to run the `main` method of the class [H1CallingGuava20.java](h1_problem-clashing-dependencies/src/main/java/com/topdesk/maven_hell/problem/H1CallingGuava20.java) we get the following error:
29 | ```
30 | Exception in thread "main" java.lang.NoClassDefFoundError: com/google/common/graph/GraphBuilder
31 | at com.topdesk.maven_hell.c.ThisClassInCDependsOnGuava20.methodOnlyInGuava20(ThisClassInCDependsOnGuava20.java:7)
32 | at com.topdesk.maven_hell.b.ThisClassInBDependsOnGuava20.methodOnlyInGuava20(ThisClassInBDependsOnGuava20.java:7)
33 | at com.topdesk.maven_hell.problem.CallingGuava20.main(CallingGuava20.java:7)
34 | Caused by: java.lang.ClassNotFoundException: com.google.common.graph.GraphBuilder
35 | ... 7 more
36 | ```
37 |
38 | The main method in `H1CallingGuava20` requires Guava 20.0 at runtime on the classpath, since it calls `GraphBuilder.undirected().build()` and that class is not present in Guava 10.0.1.
39 |
40 | Note that this is an error that appears at runtime, since we are talking about the classpath that Maven provides through transitive dependencies at runtime. So this example does not show any errors during compilation. The code in the project only directly references project B and is compiled against an already compiled version of B.
41 |
42 | How can we persuade Maven to use the version 20.0 of Guava?
43 |
44 | ## Solution 1: use a direct dependency
45 | We can use our knowledge about Maven dependency resolution to our advantage. If we make sure that a dependency declaration of Guava 20.0 is closer than 10.0.1, we are good to go. So we added a direct dependency on Guava 20.0 in [h2_solution1-direct-dependency](h2_solution1-direct-dependency/pom.xml):
46 | ```
47 |
48 | ...
49 |
50 |
51 | com.google.guava
52 | guava
53 | 20.0
54 |
55 |
56 | ```
57 |
58 | We can verify that Guava 20.0 now wins the conflict by looking at the dependency tree again with `mvn dependency:tree -Dverbose`:
59 | ```
60 | [INFO] com.topdesk.maven-hell:h2_solution1-direct-dependency:jar:0.0.1-SNAPSHOT
61 | [INFO] +- com.topdesk.maven-hell:A-depends-on-guava-10:jar:0.0.1-SNAPSHOT:compile
62 | [INFO] | \- (com.google.guava:guava:jar:10.0.1:compile - omitted for conflict with 20.0)
63 | [INFO] +- com.topdesk.maven-hell:B-depends-on-C:jar:0.0.1-SNAPSHOT:compile
64 | [INFO] | \- com.topdesk.maven-hell:C-depends-on-guava-20:jar:0.0.1-SNAPSHOT:compile
65 | [INFO] | \- (com.google.guava:guava:jar:20.0:compile - omitted for conflict with 10.0.1)
66 | [INFO] \- com.google.guava:guava:jar:20.0:compile
67 | ```
68 |
69 | And indeed we can run the `main` method in [H2CallingGuava20.java](h2_solution1-direct-dependency/src/main/java/com/topdesk/maven_hell/problem/H1CallingGuava20.java) without a `ClassNotFoundException`.
70 |
71 |
72 | But it is pretty weird to say that our project has a direct dependency on Guava, while we only have a transitive dependency on Guava. And the Maven dependency analyzer agrees with this sentiment. Running `mvn dependency:analyze` will emit the following warning:
73 | ```
74 | [WARNING] Unused declared dependencies found:
75 | [WARNING] com.google.guava:guava:jar:20.0:compile
76 | ```
77 |
78 | So this is not a nice solution. Can we do better?
79 |
80 | ## Solution 2: Dependency Management
81 | The dependency management block in Maven allows you to control which version of a dependency to use. It works for transitive dependencies, but also allows you to omit the version declaration if you depend directly on a library in the `` section. The `pom.xml` in [h3_solution2-dependency-management](h3_solution2-dependency-management/pom.xml) shows the usage of the dependency management section:
82 | ```
83 |
84 |
85 |
86 |
87 | com.google.guava
88 | guava
89 | 20.0
90 |
91 |
92 |
93 | ```
94 |
95 | This solves the warning in `mvn dependency:analyze`:
96 | ```
97 | [INFO] --- maven-dependency-plugin:2.10:analyze (default-cli) @ h3_solution2-dependency-management ---
98 | [INFO] No dependency problems found
99 | ```
100 |
101 | And `mvn dependency:tree -Dverbose` will now let us know that the version is managed:
102 | ```
103 | [INFO] com.topdesk.maven-hell:h3_solution2-dependency-management:jar:0.0.1-SNAPSHOT
104 | [INFO] +- com.topdesk.maven-hell:A-depends-on-guava-10:jar:0.0.1-SNAPSHOT:compile
105 | [INFO] | \- com.google.guava:guava:jar:20.0:compile (version managed from 10.0.1)
106 | [INFO] \- com.topdesk.maven-hell:B-depends-on-C:jar:0.0.1-SNAPSHOT:compile
107 | [INFO] \- com.topdesk.maven-hell:C-depends-on-guava-20:jar:0.0.1-SNAPSHOT:compile
108 | [INFO] \- (com.google.guava:guava:jar:20.0:compile - version managed from 10.0.1; omitted for duplicate)
109 | ```
110 |
111 | This solves our dependency clash for now. But people will probably add new dependencies over time. Dependency clashes are typically runtime problems, so how can we prevent them from turning into `ClassNotFoundException`s in production? Having automated tests helps, but it is unlikely that you have 100% percent test coverage.
112 |
113 | ## Solution 3: Maven Enforcer plugin
114 | At TOPdesk we adopted a zero-tolerance policy for duplicate dependencies. We use the [Maven Enforcer Plugin](https://maven.apache.org/enforcer/maven-enforcer-plugin/) in our Continuous Integration build to break the build if our transitive dependencies versions do not converge to a single version. We add this configuration in [h4_solution3-maven-enforcer-plugin_breaks-build](h4_solution3-maven-enforcer-plugin_breaks-build/pom.xml):
115 |
116 | ```
117 |
118 |
119 |
120 |
121 | org.apache.maven.plugins
122 | maven-enforcer-plugin
123 | 1.4
124 |
125 |
126 | enforce-banned-dependencies
127 |
128 | enforce
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | ```
141 |
142 | Running a `mvn compile` command with this configuration will result in a failed build:
143 | ```
144 | [WARNING] Rule 0: org.apache.maven.plugins.enforcer.DependencyConvergence failed with message:
145 | Failed while enforcing releasability the error(s) are [
146 | Dependency convergence error for com.google.guava:guava:10.0.1 paths to dependency are:
147 | +-com.topdesk.maven-hell:h4_solution3-maven-enforcer-plugin_breaks-build:0.0.1-SNAPSHOT
148 | +-com.topdesk.maven-hell:A-depends-on-guava-10:0.0.1-SNAPSHOT
149 | +-com.google.guava:guava:10.0.1
150 | and
151 | +-com.topdesk.maven-hell:h4_solution3-maven-enforcer-plugin_breaks-build:0.0.1-SNAPSHOT
152 | +-com.topdesk.maven-hell:B-depends-on-C:0.0.1-SNAPSHOT
153 | +-com.topdesk.maven-hell:C-depends-on-guava-20:0.0.1-SNAPSHOT
154 | +-com.google.guava:guava:20.0
155 | ```
156 |
157 | The plugin detects that Guava 10.0.1 and 20.0 are both on the classpath and thus fails the build.
158 |
159 | We use Maven dependency exclusions to exclude the transitive dependencies we don't want. Looking at the code [H5CallingGuava10](h5_solution3-maven-enforcer-plugin_fixed/src/main/java/com/topdesk/maven_hell/problem/H5CallingGuava10.java) and [H5CallingGuava20](h5_solution3-maven-enforcer-plugin_fixed/src/main/java/com/topdesk/maven_hell/problem/H5CallingGuava20.java), we can determine that Guava 20.0 is the desired version. So we want to exclude Guava 10.0.1, so we add the following exclusion, as shown in [h5_solution3-maven-enforcer-plugin_fixed](h5_solution3-maven-enforcer-plugin_fixed/pom.xml):
160 |
161 | ```
162 |
163 | com.topdesk.maven-hell
164 | A-depends-on-guava-10
165 | 0.0.1-SNAPSHOT
166 |
167 |
168 | com.google.guava
169 | guava
170 |
171 |
172 |
173 | ```
174 |
175 | Running `mvn compile` will now result in a 'Build Success', so the enforcer plugin is satisfied.
176 |
177 | Note that you sometimes run into a dependency that has different versions of a dependency in its dependency tree, i.e. it has no dependency convergence itself. You unfortunately can't specify the version to exclude with the exclusions section. Adding that section will exclude all versions of that transitive dependency. This can sometimes force you to take a direct dependency on the excluded dependency, to ensure that at least a version of that dependency ends up at your classpath.
178 |
179 | Excluding dependencies should not be taken lightly, since they in itself can be the source of `ClassNotFoundException`s. Your dependency has been compiled against a certain version of a library. Providing another version of that dependency at runtime will only work if it is binary compatible for all calls to that library. Binary compatible means that the API of the two version must be the same and with API here we mean class names, method signatures and public field names.
180 |
181 | There are tools that can show the differences in API between libraries. And there are tools that try to determine all usages of a dependency (e.g. `mvn dependency:analyze`) but determining whether you can safely swap out different versions of a library is nearly impossible. These tools for example don't work well with regards to reflection or service loading using SPI.
182 |
183 | We have achieved a small improvement so far: we went from not knowing whether runtime classpath problems would occur, to localizing where they might occur. But there are some more problems ahead. For example: what if there is no 'correct' version of a dependency?
184 |
185 | Project [h6_problem-non-binary-compatible-dependencies](h6_problem-non-binary-compatible-dependencies/pom.xml) illustrates a project which has no version of a dependency that suits all transitive requirements. [H6CallingGuava10](h6_problem-non-binary-compatible-dependencies/src/main/java/com/topdesk/maven_hell/problem/H6CallingGuava10.java) requires Guava 10.0.1 on the classpath at runtime and [H6CallingGuava20](h6_problem-non-binary-compatible-dependencies/src/main/java/com/topdesk/maven_hell/problem/H6CallingGuava20.java) needs Guava 20.0. There is no `` that will prevent `ClassNotFoundException`s.
186 |
187 | ## Solution 4: Shading
188 |
189 | To solve this issue, we preferably update the dependency on Guava in project A. If this is not possible we can also change the artifact generated in project A to include the Guava dependency in a so-called 'fat jar'. To achieve this, we modify project A to use the shade plugin as shown in [h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10](h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10/pom.xml):
190 | ```
191 |
192 |
193 |
194 |
195 | org.apache.maven.plugins
196 | maven-shade-plugin
197 | 3.1.0
198 |
199 |
200 | package
201 |
202 | shade
203 |
204 |
205 |
206 |
207 | com.google
208 | com.topdesk.maven-hell.shaded.com.google
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 | ```
218 |
219 | Instead of creating a regular jar file with only the contents of your project, the shade plugin also unpacks all your dependencies. The trick we use here to make sure that we can have Guava version 10.0.1 on the classpath, next to Guava 20.0, without interfereing is called relocation. We relocate Guava in our jar by renaming all fully qualified class names that start with `com.google` to `com.topdesk.maven-hell.shaded.com.google`. This also updates all calls to any Guava class in your jar.
220 |
221 | After running `mvn install` on this project, we update our example project to depend on this shaded version of A, as shown in [h8_solution4-use-a-shaded-jar](h8_solution4-use-a-shaded-jar/pom.xml):
222 | ```
223 |
224 |
225 |
226 | com.topdesk.maven-hell
227 | h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10
228 | 0.0.1-SNAPSHOT
229 |
230 | ...
231 |
232 | ```
233 |
234 | If you are looking at project H8 in Eclipse, you need to right click -> Maven -> Disable workspace resolution. Eclipse is clever and sees that you depend on project H7, which it has in its workspace. Eclipse will then use the classfiles that it compiled itself, instead of the shaded jar from your local Maven repository.
235 |
236 | Both [H8CallingGuava10](h8_solution4-use-a-shaded-jar/src/main/java/com/topdesk/maven_hell/problem/H8CallingGuava10.java) and [H8CallingGuava20](h8_solution4-use-a-shaded-jar/src/main/java/com/topdesk/maven_hell/problem/H8CallingGuava20.java) now run correctly. If we look at the dependency tree, we see that there is no transitive dependency on Guava 10.0.1 anymore. Maven has used the [dependency reduced pom of H7](h7_solution4-create-a-shaded-jar-of-A-depends-on-guava-10/dependency-reduced-pom.xml), generated by the shade plugin.
237 |
238 | Shading has several downsides:
239 | * In our case it is pretty easy to change Project A, since it is our project. If this would be a closed source project, then you can't change it to use the shade plugin.
240 | * Every time Project A or its dependencies change, you need to repackage everything.
241 | * Shading does not always work. Again in the case of reflection and service loading with SPI. There is a transformer to change SPI manifest files, but that isn't flawless.
242 | * Shading is slow. The plugin has to iterate through all your class files and update them.
243 | * Your application and your classpath can become really big. In this example we already get two versions of Guava on our classpath instead of only one. In the big Java monolith at our company we have about 50 times a transitive dependency on Guava. Imagine that each one of those would be shaded, even though Guava is only 2.7 MB, our software package would be almost 150 MB bigger.
244 |
245 | ## Conclusion
246 | These are some of the forms of Maven's Dependency Hell that you may encounter. It is a hard problem that not always has a satisfying solution, but I hope you now have a better understanding of your options when you are faced with the Maven Dependency Hell.
247 |
--------------------------------------------------------------------------------