├── .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 | --------------------------------------------------------------------------------