Today after reaching office the first thing that I heard from my manager is that – we are getting deployment exception , can you please look into this . I was not expecting this as my expectation was as usual he will say good morning , but this day has something else for me in the core and overall it was an interesting day at least learnt something new J .
Before we go into the detail of the problem I want to tell you root cause of that – Maven Dependency Version Conflicts. In our application we were having the multiple versions of spring-expression jars which were brought in by other artifacts as a part of transitive dependency. So, in our application we were having two versions 3.1.3.RELEASE and 3.2.3.RELEASE of spring-expression jars and ideally it should pick up the 3.2.3.RELEASE version for both during compilation and at runtime.
Compilation went fine in our case and at runtime it failed due to some class not found exception because the same class was not available in 3.1.3.RELEASE as it was bundled by maven during packaging it as a war.
Now there were two questions in front of me :
- Why Maven chose the older version jar when I have latest version of same jar available?
- How to resolve this problem and more specifically application wide, not only for spring related jars ?
So, let’s see how Maven decides which version to choose:
Maven way of resolving dependency conflicts :
Maven works on the principle of nearest wins strategy while resolving the dependency conflicts, that means whichever version it finds nearer in the tree, it will take that version and ignore the other versions. Actually I should say Maven is little bit lazy type of guy , so whenever it starts looking for a dependency it starts traversing the tree from the root and whichever version it found earlier , it will select that and returns from their without going further . If it goes further their might be a chance that it can find some newer version but as it returns from there and take the older version with it to resolve the dependencies L . Honestly speaking it is not a fault of maven because he wants to finish the job as soon as possible and have some rest. Most importantly he(maven) doesn’t know which version your application is expecting so Maven will say to you , Hey , it is yours responsibility to let me know which version you want and if you don’t tell me I will work my own way i.e. nearer the better.
In our case version 3.1.3.RELEASE was present at the 2nd level in the tree and version 3.2.3.RELEASE was at 3rd level , so maven ignored latest version i.e 3.2.3.RELEASE. Command that I executed to see the hierarchy for my spring-expression jar is -
mvn dependency:tree -Dverbose -Dincludes=spring-expression
It gave me the complete picture for spring-expression jar i.e where it is getting used, who is using it and at what all levels it is present in the tree.
Now I got the root cause of the problem, so next step is how to make sure that Maven will always chose the latest version or the version that I am telling him.
Solution to Maven Dependency Version Conflicts:
Basically I found two solution to this problem and I think 2nd one is more simple and intuitive in comparison to 1st one. We will have a look at both the solutions and then we will discuss little more over them.
- Solution 1 : The very first solution came to my mind is that somehow I have to make this version disappear from the hierarchy before Maven reaches it . In terms of Maven I have to exclude it from the source who is bringing in this version of dependency. In my case it was coming from the spring-security-core jar, so to exclude it from this I did like something this ; This solution worked for me, but it has its own limitations like suppose if in future another artifact brings in the same dependency of the same other version and maven found it nearer then again my application will start breaking , so to fix this again I have to add the exclusion filter in the dependency . I really don’t want to do that and it will be almost impossible to maintain those dependencies in future. So, we opted the second solution and till now it is working good .
- Solution 2 : As per this approach you can tell maven that you should use this version or a subset of this version while resolving the versions conflicts. The way of telling is you can define that artifact under dependencyManagement and while resolving the version Maven will consult dependencyManagement to check if it have any version information for that dependency or not, if it have then it will use the same version or subset of versions to resolve otherwise it will go by his book of resolving conflicts (nearer the better). So, this way I have defined the version that I want maven to use while resolving the conflicts.After this it worked like charm and everywhere in the application it was using the version that I have specified. I am not sure whether this is best solution to this problem, it might possible that some more intuitive solution exists for this problem.
If you have noticed that I have said that subset of this version, what I mean to say in version attribute you can also define the range also that will ask Maven to accept any version that is available in this range. Something like this -
The above statement will enforce maven to use any version which is greater than or equal to 1.0.
Request to all of my readers to drop a comment if they found anything that is not correct in the post or they have any suggestion on resolving the dependency conflicts.
Image Source : commons.wikimedia.org