We encountered a situation where Maven chose an inconsistent version of an indirect dependency, and I'd like to understand why, and how to prevent this in the future.
Our pom.xml file had the following dependencies:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
but without a direct dependency on spring-core.
Both depend on spring-core: spring-web 3.1.2.RELEASE depends on spring-core 3.1.2.RELEASE (see its pom-file), and spring-data-jpa 1.1.0.RELEASE depends on an arbitrary 3.x version of spring-core (to be precise [3.0.7.RELEASE,4.0.0.RELEASE), see its pom-file).
Combining both, I would expect Maven to choose spring-core version 3.1.2.RELEASE. However, it does not. Instead, it chooses the highest from the range [3.0.7.RELEASE,4.0.0.RELEASE), which currently is 3.2.0.RELEASE.
(Reproduction scenario: put the above pom.xml file (gist) in its own directory, and run mvn dependency:tree -Dverbose=true: for me the result is this tree (gist). I get the same result for both Maven 2.2.1 on Linux and Maven 3.0.4 on Windows.)
This seems wrong, because it is inconsistent: a version of spring-core which spring-web's pom-file does not allow is used.
(This occurred for us when spring-core 3.2.0.RC1 became available: on the next update it was suddenly selected, and we were lucky that we got a build error because of an incompatible change between spring-core 3.1 and 3.2. But next time we may not be so lucky, and have runtime errors which are very difficult to track down.)
Urghh: I just notice that the order of <dependency> declarations matters: if I put spring-web first, then spring-core 3.1.2.RELEASE is selected. What gives?
Question: How can we make Maven choose consistent versions of indirect dependencies, or at least warn if it makes a choice which goes against a version specified in a pom-file?
Update: I am asking for a general solution here. For this specific case, I know that I can get the correct behavior by adding a dependency in <dependencyManagement>, specifying that I always want spring-core 3.1.2.RELEASE. However, I would like Maven to do the Right Thing (TM) without such specific declarations.
What you expect seems logic but Maven has no chance to do this. It just resolves dependencies without the knowledge that spring-data-jpa might have somethig to do with spring-core.
The dependency resolution works as describe here and is the way you already described:
Dependency mediation - this determines what version of a dependency
will be used when multiple versions of an artifact are encountered.
Currently, Maven 2.0 only supports using the "nearest definition"
which means that it will use 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,
until Maven 2.0.8 it was not defined which one would win, but since
Maven 2.0.9 it's the order in the declaration that counts: the first
declaration wins.
So I think the only way to prevent this situation is to be aware of it and to explecitly set the version you need in your own pom.
By the way, why do you think "This seems wrong, because it is inconsistent: a version of spring-core is used which spring-web's pom-file does not allow."?
A version specification of <version>x.y</version> is only a recommendation (see 'Note' here) to use this version. If you intend to force to use this version you have to set <version>[x.y]</version>.
In your dependency declaration, you can add the part to define the indirect dependencies to not import for this dependency.
Related
I have a library project where i'm considering adding some dependencies as optional, since they will be required only for projects compiling and running on JDK9+.
However, the documentation about optional dependencies does not clarify whether it is possible to optionally depend on specific version, i.e., if i can put in my library pom file something like
<dependency>
<groupId>com.foo</groupId>
<artifactId>dependency-a</artifactId>
<version>2.0</version>
<optional>true</optional>
</dependency>
And somehow make sure that projects that depend on my library and also want to include dependency-a will depend on version 2.0.
As far as I understand, setting dependency-a as optional mean that projects that depend on my library will not transitively depend on it, but they may explicitly add it in their own dependencies if they need some additional (optional) features in my library.
Is there a way to make sure that if they want to add dependency-a they depend on a specific version?
What would happen if a project depending on my library had
<dependency>
<groupId>com.foo</groupId>
<artifactId>dependency-a</artifactId>
<version>1.0</version>
</dependency>
And version 1.0 was not compatible with version 2.0?
And if it isn't possible to enforce the version on optional dependencies, what's the point in setting the version for an optional dependency?
I have also been looking at the maven enforcer plugin as it seems to be able to handle cases like this, but I was wondering if there is a better solution.
I think that it would be more useful to add an exclusion of the library that is optional. Example:
Project A has an optional dependency O
Project B includes project A in its pom file. In case B wants to use O, nothing is changed. Otherwise, an inclusions tag is added in B's pom file inside A dependency to exclude O:
<dependency>
<groupId>project.a.group</groupId>
<artifactId>project.a.artifact</artifactId>
<version>1.4</version>
<exclusions>
<exclusion>
<groupId>o.group</groupId>
<artifactId>o.artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
I still dont understand the benefit of using the optional tag, in case we will be obliged to specify the version vs removing the optional dependency from the first project and simply adding it when needed to the second project.
My understanding of optional is the following:
If you declare a dependency as optional, it stays on your compile classpath (so you need a version), but is not transitively visible for users of your library. So users of your library would need to add the dependency themselves (with a sensible version) to their POM.
I also do not see how you want to use the enforcer plugins because users of your library will not "see" the plugins you have in your POM.
For ease of use, I would recommend to have two different jars for the different Java versions, either separated by classifier or by version (like 1.2.3-JDK8, 1.2.3-JDK9).
Let's say you have 4 projects depending on each other in chain:
A depends on B, B depends on C, C depends on D.
All projects are built using maven.
What's the best practice for keeping their versions in sync so that when someone updates project D or C it doesn't break the build of project A?
The idea behind Maven was to include modularity in different projects. This makes teams working on these projects independent and they can work on their respective modules without worrying about other teams.
But we may run into situations where a new version of dependent project has been published and we are not aware of it. If you are running Maven 2 to resolve this issue, you can use <version>LATEST</version> command in the pom.xml for that particular dependency. So it would be like -
<dependency>
<groupId>javax.portlet</groupId>
<artifactId>portlet-api</artifactId>
<version>LATEST</version>
</dependency>
But this was discontinued from Maven 3. As per official note from Maven -
Plugin Metaversion Resolution Internally, Maven 2.x used the special
version markers RELEASE and LATEST to support automatic plugin version
resolution. These metaversions were also recognized in the
element for a declaration. For the sake of reproducible
builds, Maven 3.x no longer supports usage of these metaversions in
the POM. As a result, users will need to replace occurrences of these
metaversions with a concrete version.
So it is advisable to always use concrete versions.
However, if you still want the latest versions of dependencies to be used, you can use below command in Maven command prompt -
mvn versions:use-latest-versions
This question already has answers here:
Maven : Should I keep or remove declared dependencies that are also transitives dependencies?
(2 answers)
Closed 7 years ago.
I'm a newbie in maven and I have a project that has directly dependencies with several libraries, but if I declare only one dependency in my pom.xml the project compiles and runs perfectly. It is becasuse this library have other dependencies which are automatically imported and contain my directly dependencies.
Is it recommended to add all dependencies in the pom.xml despite transitive dependencies?
What version of a dependency should I use? The highest possible version?
No, when there are transitive dependencies which are resolved properly you don't need to specify them explicitly in the pom.xml. Thus your pom is kept small and tidy.
You should use the highest stable version of dependencies in your new projects.
However there are cases when you need a different version (in most cases higher) of a transitive dependency to be used. In that case you specify the transitive dependency with the higher version in a <dependencyManagement> tag. For example if we have:
<dependencies>
<dependency> <!-- has transitive dependency of com.artifact2 v.1.0 -->
<groupId>com.group1</groupId>
<artifactId>com.artifact1</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
If we want to specify explicitly that we need the new version of com.artifact2 which is 2.0 then we add to the pom these lines:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.group1</groupId>
<artifactId>com.artifact2</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
</dependencyManagement>
It is almost always better to use you exactly those dependencies you need. Bundled dependencies often contain more then what you need. You can however, by adding exclusions in your pom.xml, make the dependencies contain only the libraries that you actually need. Often more then one 3th party library uses same common libraries (for example logging dependencies are found in a lot of dependencies). Those libraries will then cause a conflict which can be problematic at some web containers. If you are using eclipse, open your pom in Dependency Hierarchy and see how it goes from there...
Versions depend of several factors. First important thing is always to pick a RELEASE versions (unless for example when one is explicitly required to pick some newest beta, containing the newest features you have been waiting on for 3 months). Second is to figure out which newest versions of different libraries and frameworks you use can successfully work together. Newer versions are usually preferred cause they are developed last, meaning: more developer support, more probable to work with other state-of-the-art frameworks.
I've got 2 projects using Maven. The first one is a library containing utility classes and methods. The second project is an actual application that has the library as a dependency. My library uses internally a third-party library.
So these are the dependencies:
My library: depends on the third-party library
My application: depends on my library
However, I don't want the third-party library classes to be available at compile time in my application. This is because the application is supported by a large team and I want to prevent people from accidentally using methods from the third-party library in the application given that some class names and some method names are similar between the two. Of course the third-par ty library will have to be available in my application at runtime.
If the scope for all my dependencies was compile, it wouldn't achieve my goal. Is there a way to achieve this in Maven 3?
Very good question and unfortunately you can't do this using Maven 3, or 2, or any other version, because of its fundamental design. What you're asking about is actually a desired and ideal behaviour since in fact any artifact's compile dependencies should be transitive with runtime scope. However, design like this leads to some problems. As you can read at Maven's Introduction to the Dependency Mechanism about compile scope:
It is intended that [transitive dependencies of a compile dependency which are themselves compile dependencies should be considered] runtime scope instead, so that all
compile dependencies must be explicitly listed - however, there is the
case where the library you depend on extends a class from another
library, forcing you to have available at compile time. For this
reason, compile time dependencies remain as compile scope even when
they are transitive.
So, as you see, what you require is actually the proper design of this behaviour which is unfortunately impossible to implement.
Nothing has changed during the last three years, so Michal's answer is still correct: There is no way to limit transitive visibility in Maven.
However, you should consider redesigning your library to split it in an api-artifact that is necessary as compile time dependency and which itself does not depend on the third party library and an implementation artifact which is only needed as runtime-dependency and which depends on the third party library.
In your application, you can declare an explicit dependency on the third-party library using "runtime" scope.
This prevents the third-party library from being seen at compile time and thus no direct usages can sneak in. However, it will still be present at run time (since it is needed by your library).
This works, but is awkward and deserves an explanatory XML comment in the pom.
The other answers are correct. Besides working around around a missing crucial feature in maven by splitting out an artificial API-only module, you also have these alternatives:
exclude the transitive dependencies, then depend on them directly (you have to manage the version numbers yourself)
Use checkstyle import control, and CI. That way team members may use the transitives, but then maven verify will fail
Use gradle. This is a solution to many limitations of Maven
What seems to work is use <dependencyManagement> section in the pom.
You will want to check for any side effects, since it works project wide. And you have to specify each library specifically.
Following code sample allowed me to force guava (which was smurfed in the project by google guice as a compile time transitive dependency) to a runtime dependency everywhere.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
You can analyze dependencies with: mvn dependency:analyze or have the dependencies analyzed as part of the verify lifecycle phase:
https://maven.apache.org/plugins/maven-dependency-plugin/examples/failing-the-build-on-dependency-analysis-warnings.html
You can try like that:
#My application pom.xml
<dependencies>
<dependency>
<groupId>My groupId</groupId>
<artifactId>My library</artifactId>
<version>${version}</version>
<exclusions>
<exclusion>
<groupId>third-party library</groupId>
<artifactId> third-party library</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>third-party library</groupId>
<artifactId> third-party library</artifactId>
<version>${version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
If it's possible for you to migrate from Maven to Gradle you can have this issue solved, as pointed out by #tkruse. I'll explain why:
With Gradle, library authors have 2 different "compile" level scopes available: api and implementation.
The api scope behaves like Maven's compile scope and is meant for dependencies of your library that will be part of its API and are thus required to be also "compile" dependencies of the consumers of your lib.
The implementation scope however aims to solve the very problem you presented. Its meant for dependencies of your library that are not part of its API and thus only required to be "runtime" dependencies of the consumers of your lib.
This is done in a very clever way: implementation dependencies are used during compilation phase normally, but when Gradle generates the pom.xml metadata of the library (or any other type of metadata) to be published, it sets this dependency as a "runtime" dependency.
Source: https://gradle.org/maven-vs-gradle
I'm playing around with gradle trying to port my maven project and here's my problem:
In maven we have the <dependencyManagement> which provides a common (default) version for certain dependencies (which is used only when in a sub-pom this artifact is used without a version number). It also, from what I understand, forces a certain version for all transitive dependencies. So if I understand correctly even if artifact B which we have as a dependency has a dependency on artifact C version 1.0 then we will still use a version of artifact C defined in the <dependencyManagement> (so it might be 2.0). Is that correct?
If so then is there a way to do something similar in Gradle? I know that a common way of replacing the <dependencyManagement> is to simply create a Groovy map in one of the build scripts. But how can I force the transitive dependency version? If I use Gradle's "force" won't it affect all (not only transitive) dependencies (which is obviously not what I want)?
In Gradle, forcing a version (e.g. with Configuration.resolutionStrategy.force) will force it for all dependencies of the configuration, direct and transitive. There is no first-class feature that forces a version only for transitive dependencies. Do you have a valid use case for this? At the end of the day, both Gradle and Maven will select a single version for a dependency anyway, no matter where and how often it appears in the dependency tree.
There is a ResolutionStrategy feature that allows forcing artifacts versions including transitive dependencies: https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html
It is also possible to configure dependencies constraints: https://docs.gradle.org/current/userguide/dependency_constraints.html