Multiple versions of library with Gradle - gradle

I have the problem that Gradle is pulling in two versions of a library, which is causing runtime issues. These versions are both necessary, and are dependencies of two of the dependencies I have. Library A needs Library C version x, and Library B needs Library C version y. At runtime, the incorrect version of Library C is used, causing a NoSuchFieldError. Is this something I can resolve in Gradle? Or is it more of an issue for my IDE/JVM options?

In general Gradle will do the right thing by choosing the latest version of a library when there are dependency conflicts. However for various reasons this may not always work correctly. To get around this you can tell Gradle explicitly to not include a certain transitive dependency during its resolution. Here is an example:
compile (group:'com.project', name:'library', version:'1.0') {
// These lines will exclude these other libraries from being included
exclude module: 'groovy-all'
exclude module: 'log4j'
exclude module: 'commons-lang'
}
You can make this more fine grained if you need to, but I've found excluding modules seems to work well for me.

Related

Direct dependency vs Transitive dependency in build system

I was studying Maven's build system and it adds a lot of transitive dependencies because of its transitive dependency system (maven itself does not add dependencies but transitive dependency system does). I see issues with it like major version conflicts and unknown dependencies coming in.
I was thinking why is the system designed this way and why not take direct dependencies. My library does not need to depend on something which my dependency is using but not my library (I mean I understand why it needs to be included in the build list, my dependencies need to build using those, but why does it needs to cause major version conflict?). Am I missing something fundamental here? One thing that I can think of is that my library's build dependency list can grow to be very big because of all the direct dependencies I will need to take, but that does seem to be as big of a problem as problems with transitive dependency system.
I am new to build systems, so please don't be too harsh. I also tried to google this question but didn't find useful answers but please feel free to comment anything that I might have missed.
Thanks
If you need library A to run, and library A needs library B to run, and this needs C to run, it is very tedious to figure this out and add all the relevant dependencies to your project.
Before Maven and Gradle, many people worked that way and found out, that it is much easier to let a build tool figure out the transitive dependencies.
My library does not need to depend on something which my dependency is using but not my library [...]
This is your major misconception. There are two possibilities:
The direct dependency of your library exposes types from the transitive dependency in its public API. To use this public API you need to access these types, so you need the transitive dependencies during compile time.
The direct dependency of your library only uses its own dependency internally, but not in its public API. In this case, your library does not need to depend on the transitive dependency during compile time. But as soon as your library code runs (even in a test), it may use some functionality of its direct dependency that internally uses functionality of the transitive dependency, causing your library code to fail.
[...] I mean I understand why it needs to be included in the build list, my dependencies need to build using those [...]
There is no actual build list (or order) for external dependencies, because they are used when they are already built (the downloaded .jar files contain compiled .class files). But as I mentioned above, you will need the transitive dependencies either during compile time or during runtime (e.g. tests), so your build system (Maven or Gradle) will fetch them for you.
[...] but why does it needs to cause major version conflict?
#khmarbaise already explained in his comment, why and how version conflict between transitive dependencies may occur:
You are using two libs X and Y. Both of them using another lib (A) So X is using A in version 1.0.0 but Y is using A in version 2.0.0. In the end you can't have both on the classpath there must be done a decision for one version. So depending on how X,Y are implemented either X can fail while using A in V1.0.0 or Y can fail in using A in V1.0.0 or with V2.0.0 the same... This can happen if X or Y are being updated. This is also true for different version combinations like A in 1.0.0 and 1.1.0 (if compatibility is not 100%)

How to manage gradle transitive dependency conflicts?

I have a project that has a build.gradle that looks like the following:
compile project(':project1')
compile project(':project2')
compileOnly(group: 'org.scala-lang', name: 'scala-library', version: '2.10.5')
Let's say it is not easy to identify the exact dependencies that I need in "project1", which is the main reason why we have this "whole project" dependency in the first place. Assuming that project1 also depends on scala-lang but a wildly different version. At runtime, some part of our code depends on this "scala-lang" from project1 dependencies, while some other part of the code depends on the scala-lang I specified via compileOnly().
This is just one example, my actual code depends on many projects and many many more individual libraries. Let's also assume that breaking the monolithic code base into smaller and more manageable components is not doable at this time. What are some great ways to manage dependencies and transitive dependencies in Java gradle project? Thanks!

Why did a specific jar file get included?

I have a project that uses gradle and mavenCentral() (plus mavenLocal()). It has enough dependencies that I can't go through them one by one.
Given the name of a .jar file in build/install/x/lib, how do I find out the chain of transitive dependencies that caused it to be included?
update: I discovered gradle dependencies. The output shows:
org.apache.commons:commons-jexl:2.1.1
\---- commons-logging:commons-logging:1.1.1 -> 1.1.3
What does this mean? 1.1.1 is the version I expect, and 1.1.3 is the version I seem to actually end up using. Looking at the pom for commons-jexl it looks like it does indeed list logging:1.1.1 as a requirement. What's going on? Is there a way for me to tell it to avoid certain versions, or force it to use the version it was set to?
The problem in my case is that it's including a -SNAPSHOT version and I'd rather it didn't. In fact I probably want it to just use the version numbers I'm asking for instead of the most recent it can find.
Dependencies of gradle-managed project have their own dependencies (they're called transitive). It may happen (and happens quite often) that two different dependencies has the same (group and module) dependency but in the different version). This is the case with commons-logging:commons-logging. In this case there are two transitive dependencies one versioned with 1.1.1 and the second one with 1.1.3. If both of the libraries will be included in the final artifact it may result in a conflict and exception. To prevent such situation gradle tries to resolve mentioned version resolution problems by picking (by default) the latest version. It's indicated with the right arrow -> see here. You can exclude transitive dependencies from a particular dependency. This chapter of manual might be useful.

How do I prevent Gradle from exporting a dependency?

I have two Android apps that share a subproject. The build.gradle in the subproject contains a dependency like this:
dependencies {
compile "my.library.dependency:${version}"
}
What I'd like is to use this dependency at compile time, but not runtime. This is because the two apps need to link against slightly different versions of the dependency (same API).
The Gradle docs describe dependency configurations like this:
compile The dependencies required to compile the production source of
the project.
runtime The dependencies required by the production classes at
runtime. By default, also includes the compile time dependencies.
If runtime also includes compile dependencies, does this mean the library is exported from the subproject to the parent projects (and included in my apk)? If so, how do I prevent this? I assume it's possible because it says "by default".
Thanks in advance...
It's not clear if your subproject is using the android-library plugin, but if it is, v0.8 has added a provided scope -- see http://tools.android.com/recent/androidstudio043released.
This should work:
dependencies {
provided "my.library.dependency:${version}"
}
If you upgrade to 0.8 of the plugin, you'll need to run Gradle 1.10 (update gradle-wrapper.properties if you're using the wrapper), and if you're using Android Studio, upgrade to 0.4.3.

how to exclude gradle dependencies

I currently have a project that I have performed an aqua scan on, and it identified the jackson-databind-2.9.8.jar I'm currently using as a critical vulnerability, and has recommended me to replace with version 2.10. To update this, while ensuring all other dependencies/code works fine, I've tried the following code in my build.gradle file, where group_name:microservice-event:0.2.+ shows up on the list of gradle dependencies and apparently brings in the 2.9.8 jar that is causing problems:
implementation 'com.fasterxml.jackson.core:jackson-databind:2.10'
implementation('*group_name*:microservice-event:0.2.+') {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
}
I've also removed the implementation '*group_name*:microservice-event:0.2.+' line I previously had in my build.gradle file.
However, now the project fails to build and I have no idea why. Would anyone know of how to write code in the build.gradle file to successfully exclude old jars/dependencies, while allowing for newer jars (as I've tried to do with the line implementation 'com.fasterxml.jackson.core:jackson-databind:2.10'). Note that I do not want to update the spring boot version.
When Gradle encounters two different versions of the same dependency, it will perform a conflict resolution. It defaults to choosing the highest version number.
However, because many libraries like Jackson consists of a number of individual modules like jackson-databind and jackson-core, you may end up in a situation where there is a mismatch between the different versions.
To align them, you can use the Jackson BOM and Gradle's platform dependency mechanism. It looks like this (choose only one of the depencendies below):
dependencies {
// Enforce the specified version
implementation(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.10.4"))
// Align all modules to the same version, but allow upgrade to a higher version
implementation(platform("com.fasterxml.jackson:jackson-bom:2.10.4"))
}
You don't need to exclude anything from your other dependencies.
If you encounter problems with the use of Jackson after upgrading, you should have a look at the release notes for 2.10 and check if you might be hit by any of the compatibility changes. Of cause, if the problem is in a third-party library, it might be more difficult to fix. But you may try the latest version in the 2.9 line (which is 2.9.10 at this time) and see if the vulnerability is fixed here.

Resources