{"id":924,"date":"2023-07-15T00:23:27","date_gmt":"2023-07-15T00:23:27","guid":{"rendered":"https:\/\/www.evan.org\/ghg\/?p=924"},"modified":"2023-07-15T01:29:06","modified_gmt":"2023-07-15T01:29:06","slug":"debugging-maven-dependency-conflicts","status":"publish","type":"post","link":"https:\/\/www.evan.org\/ghg\/command-line\/maven\/debugging-maven-dependency-conflicts\/","title":{"rendered":"Debugging Maven Dependency Conflicts"},"content":{"rendered":"\n<p><br>You bring in dependencies, they bring in more dependencies &#8211; so how does maven decide which dependencies to bring in if there conflicts? This usually happens if two dependencies you add bring in libraries at different versions. This happens a lot, and if the versions are compatible with each other then you usually just don\u2019t notice. But the first important thing to understand is how Maven is doing this.<\/p>\n\n\n\n<p>If you declare a dependency it\u2019s easy. Maven uses the version you declare. Transitive dependencies are the difficult ones &#8211; the dependencies brought in indirectly.<\/p>\n\n\n\n<p>Maven resolves dependencies by picking the dependency closest to your project. If your project depends on project B, project B depends on project C, and project C depends on project D version 1, you get project D version 1.<\/p>\n\n\n\n<p><code>A -&gt; B -&gt; C -&gt; Dv1<\/code><\/p>\n\n\n\n<p>But if you add a dependency on project E which depends on project D version 2, you now will get project D version 2 as that is closer to your project.<\/p>\n\n\n\n<p><code>A -&gt; B -&gt; C -&gt; Dv1<br>E -&gt; Dv2<br>Resolution: Dv2<\/code><\/p>\n\n\n\n<p class=\"has-text-align-left\">If you then include a dependency on artifact D version 3, it will include artifact D version 3 as that is the closest.<br><code>A -&gt; B -&gt; C -&gt; Dv1<br>E -&gt; Dv2<br>Dv3<br>Resolution: Dv3<\/code><\/p>\n\n\n\n<p>An edge case is if two versions are at the same transitive depth:<br><code>A-&gt;Dv1<br>B-&gt;Dv2<\/code><br>In this case, the first one wins &#8211; so if A is first in your pom, you will get Dv1. If B is first in your pom, you will get Dv2.<\/p>\n\n\n\n<p>See Maven documentation for more information: <a href=\"https:\/\/maven.apache.org\/guides\/introduction\/introduction-to-dependency-mechanism.html\">https:\/\/maven.apache.org\/guides\/introduction\/introduction-to-dependency-mechanism.html<\/a><\/p>\n\n\n\n<p>The problem here usually comes in when you add or update a dependency. Most of the time it works just fine. But once in a while not so much.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"500\" height=\"281\" src=\"https:\/\/www.evan.org\/ghg\/wp-content\/themes\/maktub\/assets\/images\/transparent.gif\" data-lazy=\"true\" data-src=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/regret.gif\" alt=\"\" class=\"wp-image-923\"\/><\/figure>\n\n\n\n<p>It seems so easy &#8230; you want to update your dependencies. Maven even makes it easy, this command shows you all dependencies that have an update available and what the latest version is:<\/p>\n\n\n\n<p><code>mvn versions:display-dependency-updates<\/code><\/p>\n\n\n\n<p>And this command does the same for plugins:<\/p>\n\n\n\n<p><code>mvn versions:display-plugin-updates<\/code><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"496\" height=\"589\" src=\"https:\/\/www.evan.org\/ghg\/wp-content\/themes\/maktub\/assets\/images\/transparent.gif\" data-lazy=\"true\" data-src=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/POM-Updates.jpg\" alt=\"\" class=\"wp-image-937\" data-srcset=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/POM-Updates.jpg 496w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/POM-Updates-253x300.jpg 253w\" data-sizes=\"(max-width: 496px) 100vw, 496px\" \/><\/figure>\n\n\n\n<p><strong>So how can I tell what is being bought in?<\/strong><br>My goto command is <\/p>\n\n\n\n<p><code>mvn dependency:tree -Ddetail=true<\/code><\/p>\n\n\n\n<p>This shows you a tree of dependencies and what they bring in.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"636\" height=\"191\" src=\"https:\/\/www.evan.org\/ghg\/wp-content\/themes\/maktub\/assets\/images\/transparent.gif\" data-lazy=\"true\" data-src=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTree.png\" alt=\"\" class=\"wp-image-925\" data-srcset=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTree.png 636w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTree-300x90.png 300w\" data-sizes=\"(max-width: 636px) 100vw, 636px\" \/><\/figure>\n\n\n\n<p>This shows that we are using Dropwizard-core version 2.1.6. It is bringing in Dropwizard-Util, etc. Note the nesting &#8211; so for example, dropwizard-core brings in dropwizard-logging, and that brings in metrics-logback.<\/p>\n\n\n\n<p>This does show a pruned list &#8211; if a lower level artifact is included higher in the tree then this won\u2019t show it. You can get a full list with:<\/p>\n\n\n\n<p><code>mvn dependency:tree -Ddetail=true -Dverbose=true<\/code><\/p>\n\n\n\n<p>This output is the same as above, but with the verbose flag set:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1023\" height=\"409\" src=\"https:\/\/www.evan.org\/ghg\/wp-content\/themes\/maktub\/assets\/images\/transparent.gif\" data-lazy=\"true\" data-src=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTreeVerbose.png\" alt=\"\" class=\"wp-image-926\" data-srcset=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTreeVerbose.png 1023w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTreeVerbose-300x120.png 300w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTreeVerbose-768x307.png 768w\" data-sizes=\"(max-width: 1023px) 100vw, 1023px\" \/><\/figure>\n\n\n\n<p>You can see something similar in IntelliJ. Go to view-&gt;tool windows-&gt;Maven. In the Maven tool window, browse to your project and pick \u201cdependencies\u201d. You will see the same information as the verbose output of Maven:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"696\" height=\"485\" src=\"https:\/\/www.evan.org\/ghg\/wp-content\/themes\/maktub\/assets\/images\/transparent.gif\" data-lazy=\"true\" data-src=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTreeIntelliJ.png\" alt=\"\" class=\"wp-image-927\" data-srcset=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTreeIntelliJ.png 696w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepTreeIntelliJ-300x209.png 300w\" data-sizes=\"(max-width: 696px) 100vw, 696px\" \/><\/figure>\n\n\n\n<p>The maven command above is using the Dependency plugin, which exists to help you figure out dependency issues: <a href=\"https:\/\/maven.apache.org\/plugins\/maven-dependency-plugin\/\">https:\/\/maven.apache.org\/plugins\/maven-dependency-plugin\/<\/a><\/p>\n\n\n\n<p>(mvn dependency:resolve is also very useful, it will show how all dependencies resolve.)<\/p>\n\n\n\n<p><strong>Effective Poms<\/strong><br>Your pom might have a parent pom, it might have a parent pom, etc. It can be difficult to even know what versions of things are being imported!<\/p>\n\n\n\n<p>You can see what is known as an \u201ceffective pom\u201d, which is essentially the pom and every parent all mixed together, also with resolved profiles and any other relevant setting.<\/p>\n\n\n\n<p>From the commandline you can see the effective pom by running:<br><code>mvn help:effective-pom<\/code><br>I prefer seeing it in IntelliJ, right click a pom file and choose Maven-&gt;Show Effective Pom and it\u2019ll show up in a new tab.<\/p>\n\n\n\n<p>Generating an effective pom with and without a change and then doing a diff on them can be very helpful &#8211; though for dependency issues it is usually helpful when you change to a more recent parent pom and then find things breaking.<\/p>\n\n\n\n<p><strong>Everything resolves, so what\u2019s the problem?<\/strong><br>Not every version of a library is compatible with other versions. A call might have one parameter in version one, but two parameters in version 2. Or they might have changed a namespace. Or anything else that means the version of the library abruptly becomes quite important.<\/p>\n\n\n\n<p>The break is not apparent, you do not get a clear error message saying that you have incompatible library versions. Quite often everything will build, and all unit tests will run &#8211; but then if you try to start the service it won\u2019t start. The worst case is sometimes it will start, but eventually you will do something that triggers a call to the incompatible library which fails.<\/p>\n\n\n\n<p>Example error messages are hard to give as they are fairly random. \u201cNo Such Method\u201d types of errors are common &#8211; if the calls to the library changed then calls to the wrong version will give you this error. In general, if you see error messages around calls in a library you aren\u2019t familiar with and didn\u2019t call, then that\u2019s a clue. Googling error messages will often tell you it\u2019s a library mismatch. And sometimes you get a random error and honestly the best thing to do is if you wonder, then remove all your code changes and try running code with just the updated dependencies and see what happens.<\/p>\n\n\n\n<p>If you see exceptions like these you should be suspicious of a dependency issue:<\/p>\n\n\n\n<ul>\n<li>java.lang.NoSuchMethodError<\/li>\n\n\n\n<li>java.lang.NoSuchMethodException<\/li>\n\n\n\n<li>java.lang.NoSuchFieldError<\/li>\n\n\n\n<li>java.lang.ClassNotFoundException<\/li>\n\n\n\n<li>java.lang.NoClassDefFoundError<\/li>\n<\/ul>\n\n\n\n<p>To really prove that dependency updates are safe you will need to not only run your unit and integration tests, but actually bring up the service. I have frequently seen dependency upgrades pass all tests, but cause a service to be unable to start.<\/p>\n\n\n\n<p><strong>Finding the Dependency To Fix<\/strong><\/p>\n\n\n\n<p>If you have doubts about your dependencies the first step is to isolate the changes. Remove the code changes and just update the pom.This lets you know for sure that the issue is a dependency, and not a code change.<\/p>\n\n\n\n<p>Now find the pom change that caused the issue. Ideally just apply each pom change one at a time and see if the issue replicates. There might be more than one pom change causing an issue &#8211; so you do need to test them all. Honestly I usually do a binary search &#8211; put in half the changes and if things work then put in half of the remaining changes and continue.<\/p>\n\n\n\n<p>This should give you the dependency that is causing the problem, but not which transitive dependency is the actual issue. To find that, remember the command from above:<\/p>\n\n\n\n<p><code>mvn dependency:tree -Ddetail=true -Dverbose=true<\/code><\/p>\n\n\n\n<p>Remove the problematic change, and run that command, dumping the output into a file:<br>mvn dependency:tree -Ddetail=true -Dverbose=true &gt; pre<br>Replace the change and run that command again, dumping the output into another file:<br>mvn dependency:tree -Ddetail=true -Dverbose=true &gt; post<br>Now run a diff on the pre and post files. That will show you every library that changed &#8211; and now you have a list of suspects.<\/p>\n\n\n\n<p>Sometimes that\u2019s enough and you can figure it out from there (take a few tries using excludes in the pom file and see!)<\/p>\n\n\n\n<p>If not, then you might need to find out for sure exactly what is happening in what library. To do that, run the code in IntelliJ and replicate the issue &#8211; ideally from a unit test as that\u2019s simpler to bring up, but if you have to run the service then go ahead. Then look at the callstack that failed, and set a breakpoint in it.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"315\" src=\"https:\/\/www.evan.org\/ghg\/wp-content\/themes\/maktub\/assets\/images\/transparent.gif\" data-lazy=\"true\" data-src=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepCallstack-1024x315.png\" alt=\"\" class=\"wp-image-928\" data-srcset=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepCallstack-1024x315.png 1024w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepCallstack-300x92.png 300w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepCallstack-768x236.png 768w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepCallstack.png 1103w\" data-sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Set a breakpoint in that callstack &#8211; I usually start with the lowest spot in the codebase (so com.usermind.saiga in the stack above) but often will go lower as well if needed. When I step into the library called from Saiga, look at the project window in IntelliJ:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"439\" height=\"455\" src=\"https:\/\/www.evan.org\/ghg\/wp-content\/themes\/maktub\/assets\/images\/transparent.gif\" data-lazy=\"true\" data-src=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepCallstackIntelliJ.png\" alt=\"\" class=\"wp-image-929\" data-srcset=\"https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepCallstackIntelliJ.png 439w, https:\/\/www.evan.org\/ghg\/wp-content\/uploads\/2023\/07\/DepCallstackIntelliJ-289x300.png 289w\" data-sizes=\"(max-width: 439px) 100vw, 439px\" \/><\/figure>\n\n\n\n<p>That shows me I stepped into Dropwizard-jdbi3 version 2.1.7. Now I can step into that library and just step over lines until something blows up. Next time I will step into the line of code that blew up and repeat. The goal here is watching the project window to the left to see what libraries and versions it is stepping into &#8211; that helps me build a picture of what exactly is happening. You will need to write down what is happening with both libraries and versions &#8211; because the next step is to revert the code so no dependencies are updated and do the same thing. This gives you a comparison of what libraries it went into when it was working, and what libraries it went into when it failed.<\/p>\n\n\n\n<p>When you do this there are two things to look for &#8211; what library versions do you step into that changed? And also &#8211; did the behavior change? If so, why?<\/p>\n\n\n\n<p>This isn\u2019t something I have to do often, but occasionally it can really help &#8211; so don\u2019t be afraid to step into third party code!<\/p>\n\n\n\n<p><strong>The Toolkit<\/strong><\/p>\n\n\n\n<p>So you have theories about where the conflict is &#8211; so then what?<\/p>\n\n\n\n<p>The maven dependency commands above will show you which libraries are being pulled in. If you see library A is pulling in library C at version 1, and library B is pulling in library C at version 2 and there is a conflict, try the following steps in order, stopping when the issue resolves.<\/p>\n\n\n\n<p><em><strong>IMPORTANT NOTE<br>If you are testing in IntelliJ, don\u2019t forget to tell it to reimport the Maven project each time after you make pom changes!<\/strong><\/em><\/p>\n\n\n\n<p><strong>Step 1<\/strong>: Best Path (but you have to get a little lucky)<br>Update both libraries A and B to the latest. If you are lucky they will both have updated their dependencies and the problem goes away. This is the best solution, and will often work.<\/p>\n\n\n\n<p>(Note that \u201cmvn versions:display-dependency-updates\u201d will show you which dependencies can be updated and what the latest version is. \u201cmvn versions:display-plugin-updates\u201d does the same for plugins.)<\/p>\n\n\n\n<p><strong>Step 2<\/strong>: Exclude the dependent version you don\u2019t want<br>In the example, let\u2019s decide we want library C version 2. We can go to dependency A and tell Maven to ignore its version of library C. Maven will then choose from the other options, which in this case is from library B\u2019s dependencies. Note that if multiple libraries bring in library C version 1, we will have to add an exclusion to each of them.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  &lt;dependency&gt;\n    &lt;groupId&gt;io.dropwizard&lt;\/groupId&gt;\n    &lt;artifactId&gt;dropwizard-jersey&lt;\/artifactId&gt;\n    &lt;exclusions&gt;\n      &lt;exclusion&gt;\n        &lt;groupId&gt;commons-logging&lt;\/groupId&gt;\n        &lt;artifactId&gt;commons-logging&lt;\/artifactId&gt;\n      &lt;\/exclusion&gt;\n      &lt;exclusion&gt;\n        &lt;groupId&gt;ch.qos.logback&lt;\/groupId&gt;\n        &lt;artifactId&gt;*&lt;\/artifactId&gt;\n      &lt;\/exclusion&gt;\n    &lt;\/exclusions&gt;\n  &lt;\/dependency&gt;<\/code><\/pre>\n\n\n\n<p>The first exclusion is to not bring in the commons-logging dependency. But notice the artifact in the second exclusion &#8211; it\u2019s a wildcard. That will tell Maven not to bring in any libraries from ch.qos.logback. That is sometimes much simpler than listing out multiple exclusions.<\/p>\n\n\n\n<p><strong>Step 3<\/strong>: Pick equivalent releases<br>If both of those approaches fail, go to the central Maven Repository at https:\/\/mvnrepository.com\/ and look at libraries A and B. If Library A was last updated one month ago but Library B was last updated one year ago, try using a version of Library A from one year ago. Look at the dependency output and see if they line up. You can see if things work and you can also try varying versions of libraries A and B to see if you can get ones that are compatible with each other.<\/p>\n\n\n\n<p>I have had to do this when there are very closely interconnected dependencies. And I have only had to do this a very few times.<\/p>\n\n\n\n<p><strong>Step Never<\/strong>: You can include library C yourself at the desired version. Since you included it, it\u2019ll just work! Right? Well \u2026 as long as you picked the right version, sure. I called it step Never because ideally you want to let libraries handle their own dependencies &#8211; and now you\u2019ve inserted yourself into that path. So if you update library A you will need to remember to update library C to match.This will cause you major headaches next year when you upgrade library A but have no recollection about Library C. Plus you\u2019re just adding junk into your pom to maintain. So yeah, it\u2019s something you can do, but it isn\u2019t a great way to resolve the issue.<\/p>\n\n\n\n<p><strong>BIG NOTE<\/strong><\/p>\n\n\n\n<p>This is never easy. I will often keep making changes and excluding libraries and just trying things until I get something that works &#8211; if it&#8217;s a minor conflict then no big deal, but wait until you have four libraries that all have to be at equivalent versions and all of which bring in a set of conflicting dependencies &#8230;<\/p>\n\n\n\n<p>So when I get something working, sometimes there is a mess. Most likely the last change is the one that worked, so go back and start rolling out the prior changes. If it didn&#8217;t help, then it shouldn&#8217;t get checked in. Just pick a set of changes, comment them out, and test. If things still work, but another set of changes and repeat. Just don&#8217;t check in all the false paths you tried, it&#8217;s just adding more complications that will cause you a headache later!<\/p>\n\n\n\n<p><strong>Conclusions<\/strong><br>This is more of an art than a science, unfortunately. Find the minimal change set to go from a good state to a broken state, and then either update dependencies to compatible versions or exclude transitive dependencies until things work. The trick is figuring out what changes to make, and that is often just not easy!<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>For when you get a small task to update some Maven dependencies and then two weeks later find yourself explaining to your manager what a Sisyphean Task is <\/p>\n","protected":false},"author":1,"featured_media":921,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[26],"tags":[27],"_links":{"self":[{"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/posts\/924"}],"collection":[{"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/comments?post=924"}],"version-history":[{"count":8,"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/posts\/924\/revisions"}],"predecessor-version":[{"id":943,"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/posts\/924\/revisions\/943"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/media\/921"}],"wp:attachment":[{"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/media?parent=924"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/categories?post=924"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.evan.org\/ghg\/wp-json\/wp\/v2\/tags?post=924"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}