I have a bit of a dependency hell situation here that I'm trying to resolve: I have three projects, A, B and C. A and B both depend on C. Now A is my own module that I have direct control over, B is a library I'm using, C is a library that is used both directly from my module A and by my dependency B.
To visualize this:
C
^
/ B
| ^
\ /
A
For various reasons I now needed to make a small change to C that I need locally but don't want to (or can't) deploy to the global repository from which C is downloaded normally.
I tried to do this by tagging my modified version of C installed in my local repository with a classifier and changing the dependency to it in A's POM to include the classifier like this:
<dependency>
<groupId>foo</groupId>
<artifactId>C</artifactId>
<version>0.7.16</version>
<classifier>myclassifier</classifier>
</dependency>
But mvn dependency:tree now shows that I have both the version with the classifier and the version without it in my classpath because of the transitive dependency:
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) # A ---
[INFO] org.example:A:jar:0.1-SNAPSHOT
...
[INFO] +- foo:C:jar:myclassifier:0.7.16:compile
[INFO] +- bar:B:jar:3.2.5:compile
[INFO] | +- foo:C:jar:0.7.16:compile
Is there any way I can force my project / maven to just use my modified version in this context?
Edit: Solution
For now, I've solved it using exclusions like this in A's pom.xml thanks to the answers by Filipe and Samuel.
<dependency>
<groupId>foo</groupId>
<artifactId>C</artifactId>
<version>0.7.16</version>
<classifier>myclassifier</classifier>
</dependency>
<dependency>
<groupId>bar</groupId>
<artifactId>B</artifactId>
<version>3.2.5</version>
<exclusions>
<exclusion>
<groupId>foo</groupId>
<artifactId>C</artifactId>
</exclusion>
<exclusions>
</dependency>
Note: This of course only works as long as the edits in C are really minor and don't change the API used by B, which for me is the case.
You're saying that C is the official version of the library and that your minor change (Let's call it C2) would only be used by A. You're also saying that by declaring in A the direct dependency on C2, than it's unfortunately expected that you end up with C2 and C. C will be a transitive dependency from B. Unfortunately too, the Maven reactor treats libraries with the same version but with a different classifier as different libraries, giving you exactly this result (of having both C and C2).
If you only need C2 in project A, you could explicitly add exclusions to C into A's POM.
Obviously this new version of C is for you not a new artifact, but a new version of this artifact.
So you should not use here a classifier but a version number increase instead.
Your project will be built with the last version (and even if you have a problem to resolve the last version, you can exclude the transitive dependency in your pom)
However, you must be very carefull when doing this, because your B project will have been compiled with the old version of C. So if you modify method erasure, or remove methods or classes, you will have runtime trouble.
If you only add classes or methods in your C library, then it should work.
Related
I have two projects A, and B.
Project A depends on Project B. Project B depends on a library C which depends on a library D.
I own both A and B but they live in separate code bases and are independent. Project B is like an internal common helper library.
- A
-- B
--- C (version 1.1)
---- D (version 1.1)
I want to upgrade D to a newer minor version, let's say 1.5.
So in B's pom.xml file, I imported D (v1.5) directly. Then excluded D from C. Example:
<dependency>
<groupId>D</groupId>
<artifactId>foobar</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>C</groupId>
<artifactId>fizzbuzz</artifactId>
<version>1.1</version>
<exclusions>
<exclusion>
<groupId>D</groupId>
<artifactId>foobar</artifactId>
</exclusion>
</exclusions>
</dependency>
Next if I run
[~/projectB] $ mvn dependency:tree
I confirm that only 1.5 is used, not 1.1. Next I build a new local snapshot of B. And specify Project A to use that one.
My problem is when I build Project A, it continues to use v1.1 and ignores the exclusion in B.
This always returns 1.1 instead of the 1.5 that I want.
[~/projectA] $ mvn dependency:tree
Project A never imports C or D directly, it only get library D from importing Project B.
I've tried:
Reloading the maven imports
Running mvn clean install numerous times.
Deleting the folders from my .m2/ directory to force reimports
I'm out of troubleshooting ideas. Does anyone else have any tips?
I have a maven project A that uses some Java library B, which is dependency stated in POM. That library uses some other library C that uses library D. The problem arises in compiling where both A and C are using that library D but different versions. It looks like this:
A ----- B
| |
D(v1) C
|
D(v2)
A can not change its dependency to D(v2), but also if I exclude D(v2) in A's POM:
<dependency>
<groupId>...</groupId>
<artifactId> C </artifactId>
<exclusions>
<exclusion>
<groupId>...</groupId>
<artifactId> D </artifactId>
<version> v2 </version>
</exclusion>
</exclusions>
</dependency>
I will get compilation error that some class MissingClass is not resolved in class C. The most annoying thing is that only D(v2) has that Missing Class.
I am not somewhat skilled with Maven so is there some way to make Maven is using D(v1) dependency in A's case, and D(v2) in B's case?
This is more a problem of Java classpath.
Could your scenario work if you run things via cmd line, without Maven? (i.e. run your app, and manually specify your classpath).
If the answer is yes, then you probably can do it in Maven; but my understanding from your description is that no, you couldn't run via cmd line.
Our maven project includes one 3rd third jar, which has two different versions.
A-1.0.jar (with function A() removed in version 2)
A-2.0.jar (with function B() added (not in version 1))
How to organize my codes to support 1.0's api and 2.0's api at the same time ?
A possible way of doing this is to have no dependency on library A (not version 1.0, nor version 2.0) in your project.
You need to create three other build (maven for example) projects / artifacts, which will be wrappers for the different library versions.
You'll have :
AbstractWrapper project artifact
Wrapper of version 1 artifact
Wrapper of version 2 artifact
In the AbstractWrapper, you'll have an interface / abstract class, that reproduce the api from the library A you want to use. This is the only [of the 3] artifacts that will be declared in the dependencies of your main project. This abstract wrapper do not depend on your project, nor on lib A.
Then each wrapper will depend on AbstractWrapper (to get the interface definition), and on the A library, either version 1 or version 2.
The tricky point now is to get the wrapper implementation (that will be available at runtime) to register in your main project. It depends a lot on the packaging and the runtime environnements (standalone jar, osgi bundle, servlet spec 2.5-, servlet spec 3.0+, springframework...), i let you do some research on plugin registration for your specific environnement.
The ugliest thing you could come up with is a Class.forName() trying both implementation you did, and using the first it finds (casting the class you get to the abstract wrapper interface).
Finally for the clients using the version 1.0 of the lib A, you tell them to depends on your main project and your wrapper of lib version 1, and the others on your main project and lib version 2.
You can't. But you can exclude one or the other version with the tag like this:
<dependency>
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>sample.ProjectD</groupId> <!-- Exclude Project-D from Project-B -->
<artifactId>Project-D</artifactId>
</exclusion>
</exclusions>
</dependency>
See the documentation here: https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html
In my project there is a class implementing an interface. The interface comes from a dependency. I have another dependency that itself has a dependency on a jar that also contains the same interface, except a version with more methods; the two jars containing the same package-interface DON'T have the same groupId or artifactId.
Compilation is failing because the compiler complains that the class in my project is not implementing all the methods. I realized it is because the compiler is taking the interface reference from the wrong jar. My question is, why is maven using the interface from the transitive dependency instead of the one from the jar that I explicitly mention in the project POM? I can see the jar that is used appears earlier in the definition (so i imagine in the classpath as well), but i thought in these cases maven resolved it by using the colliding class/interface from the dependency with the shortest path
Here is part of the dependency tree. Note that this is grepped but it can still be seen that javax.servlet:servlet-api (the one actually used) is deeper in the tree compared to tomcat:servlet (the one that should be used)
[builder#ca-rd-build11 proj]$ mvn dependency:tree | grep servlet
[INFO] | +- javax.servlet:servlet-api:jar:2.4:compile
[INFO] +- tomcat:servlet:jar:4.0.6:compile
I'm using maven 3.0.4
why is maven using the interface from the transitive dependency
instead of the one from the jar that I explicitly mention in the
project POM?
Because, to Maven, the two have nothing to do with each other. Maven doesn't know about class or package names, Maven just knows about groupId and artifactId. Since those are not the same, maven doesn't have any reason to omit the transitive dependency.
And if I am correct, Maven places the dependencies on the classpath in the order they are defined, i.e. the transitive dependencies of dependency a appear before dependency b.
When you declare your dependency on the other jar file you can tell it to exclude the transitive dependency on the conflicting jar file:
<dependency>
<groupId>group</groupId>
<artifactId>artifact</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>othergroup</groupId>
<artifactId>ArtifactToExclude</artifactId>
</exclusion>
</exclusions>
</dependency>
For future visitors (since the title is generic):
In Maven you have two categories of dependencies:
(external) dependencies: what you define directly in your pom.xml files inside <dependencies>
transitive dependencies: the dependencies your pom.xml dependencies require (automatically included, together with their dependencies, and so on)
As per the official Maven documentation the following mediation is applied when multiple versions are encountered as dependencies:
Maven picks the "nearest definition". That is, it uses the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project's POM. Note that if two dependency versions are at the same depth in the dependency tree, the first declaration wins.
Example: if dependencies for A, B, and C are defined as A -> B -> C -> D 2.0 and A -> E -> D 1.0, then D 1.0 will be used when building A because the path from A to D through E is shorter. You could explicitly add a dependency to D 2.0 in A to force the use of D 2.0.
In the above example, A is the actual project, B and E are external dependencies defined in its pom.xml file, and C and D are transitive dependencies.
My project is made of 5 sub projects. One is a War, and the other 4 are jars. Basically the war project needs all 4 jar projects, and their dependencies.
I can strip down the dependencies to have something like war->A->B->C->D. Every sub project add their share of external dependencies (spring, struts, hibernate) so that in the end the war gets everything needed to run.
This looks pretty well organised and square, but then I ask myself if this is very practical to make changes.
Imagine I have to change one line of code in project D, without changing anything to its Maven dependencies. I would have to re-release project D obviously, but then I have to re-release projects C, B, A, and the war just to reflect this change in their pom files. This can be long and annoying, especially if you have to quickly release a new version to fix something in production.
I could make the war depend on all 4 projects, so then I just have to change project D version number in the war pom file. But then I have project A depending indirectly on project D 1.0 and the war specifying project D 1.1. I think that the war direct dependency would win in that case wouldn't it ?
This would make the new war release quicker, but it would also mess my sub projects dependencies, as they would be outdated.
What would be an acceptable way to handle this situation ?
There is no simple answer to your problem.
If you indeed do have a chain of transitive dependencies (A->B->C->D), then releasing each modules up the chain independently is not a bad option. Although it is tedious, there is a good chance your nested dependencies are simple lib jars and will not see changes too often. Hopefully you will not be forced to go through that process frequently. Pretend it would be the same situation as if log4j was updated and all of your modules needed to be updated as well.
Another thing to consider is your WAR's dependencies. Yes, Maven will pull dependencies in automatically for you but it is often a good practice to declare your known dependencies explicitly so you can specify a version number yourself for each module. This would mean A depends on D and the others directly. Unfortunately, if you have conflicting version numbers, as you've described, then you are looking for trouble on your classpath. If you really need to do this though, maven does allow you exclude transitive dependencies explicitly:
<project>
...
<dependencies>
<dependency>
<groupId>my.project</groupId>
<artifactId>module-B</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>my.project</groupId>
<artifactId>module-C</artifactId>
</exclusion>
<exclusion>
<groupId>my.project</groupId>
<artifactId>module-D</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>my.project</groupId>
<artifactId>module-C</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>my.project</groupId>
<artifactId>module-D</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>my.project</groupId>
<artifactId>module-D</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
...
</project>
Here is the documentation describing these optional dependencies and exclusions.
Do you actually need to release B, C, and D independently? If not, consider using an Aggregator pom.xml file at the root of your modules. This will allow you to use SNAPSHOT versions throughout your modules and then release the bunch at once. This is the way our team manages our multi-module project. Using SNAPSHOT dependencies ensures you use the version that was JUST built when those artifacts are needed.
Do you actually release any of projects A to D independently, without the WAR? If not, I don't see any problems with your current setup. You should absolutely use the same version of any module throughout the project. Otherwise you open the door to classloader hell - believe me, you don't want to get there :-(
To make releases easier, the maven-release-plugin may help you.
The best answer these days is now use gradle which is best of ant and maven. I never really liked maven but gradle took alot of the common concepts but made it more like ant in that it is flexible so that there is no an easy answer to your question in gradle ;).