Skip to Content
Course content

122: Managing Dependencies and Versions

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You're building a feature, you've added a couple of libraries to your pom.xml or build.gradle, everything compiles perfectly, and you hit "Run." Then, out of nowhere, your application crashes with this:

java.lang.NoSuchMethodError: com.fasterxml.jackson.databind.ObjectMapper.readerForUpdating(java.lang.Object)
    at com.example.payments.PaymentProcessor.process(PaymentProcessor.java:42)
    at com.example.Main.main(Main.java:10)

This is one of the most frustrating errors in Java because the compiler didn't warn you. You check your code, and readerForUpdating definitely exists in the documentation for the version of Jackson you think you're using. I've spent more hours than I care to admit chasing these "phantom" errors. The culprit isn't your code—it's a version conflict in your dependency graph.

The Clash of Transitive Dependencies

Here is what's happening under the hood. Your project likely looks something like this:

  • Your Project $\rightarrow$ PaymentSDK (v2.0) $\rightarrow$ Jackson (v2.15)
  • Your Project $\rightarrow$ AnalyticsLib (v1.0) $\rightarrow$ Jackson (v2.9)

Maven and Gradle can't load two different versions of the same class into the same ClassLoader. They have to pick one. If the build tool decides to use version 2.9 (perhaps because it appeared first in the resolution tree), but the PaymentSDK was compiled against 2.15 and tries to call a method that didn't exist yet in 2.9, you get the NoSuchMethodError at runtime.

I call this the "Diamond Dependency" problem. You aren't even calling the problematic library directly; it's being dragged in by your dependencies.

Hunting Down the Culprit

You can't fix what you can't see. When this happens, stop guessing and look at the actual tree. If you're using Maven, run this in your terminal:

mvn dependency:tree

I usually pipe this into a text file or grep for the library in question: mvn dependency:tree | grep jackson. This will show you exactly who is requesting which version. You'll see something like this:

[INFO] com.example:my-app:jar:1.0-SNAPSHOT
[INFO] +- com.example.payments:PaymentSDK:jar:2.0:compile
[INFO] |  \- com.fasterxml.jackson.core:jackson-databind:jar:2.15:compile
[INFO] \- com.example.analytics:AnalyticsLib:jar:1.0:compile
[INFO]    \- com.fasterxml.jackson.core:jackson-databind:jar:2.9:compile

Now the mystery is gone. AnalyticsLib is pulling in an ancient version of Jackson that is breaking your PaymentSDK.

Forcing a Version and Excluding the Noise

You have two main ways to fix this. The first is the "surgical" approach: tell the build tool to ignore the version coming from the problematic library using an exclusion.

<dependency>
    <groupId>com.example.analytics</groupId>
    <artifactId>AnalyticsLib</artifactId>
    <version>1.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </exclusion>
    </exclusions>
</dependency>

By doing this, you're telling Maven: "I want AnalyticsLib, but don't let it bring its own version of Jackson." Now, the only version of Jackson left in the project is 2.15 from the PaymentSDK, and the crash disappears.

The second approach is using a dependencyManagement section (in Maven) or a constraints block (in Gradle). This is what I prefer for larger projects. Instead of excluding the library in five different places, you define a "source of truth" at the top of your file. This tells the build tool: "Regardless of what any library asks for, ALWAYS use version 2.15 for Jackson."

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.15</version>
        </dependency>
    </dependencies>
<dependencyManagement>

Just a word of caution: when you force a version, you're gambling a bit. You're betting that AnalyticsLib (which wanted v2.9) is compatible with v2.15. In the Java world, this usually works if the library follows Semantic Versioning, but always run your integration tests after making these changes.




📋 Practical Task

Resolving the Conflict in the Order-Processor Project

You are working on an Order-Processor project. You have added two dependencies: Shipping-API and Tax-Calculator. Upon running the application, it crashes with a java.lang.NoSuchMethodError related to the guava library.

The current state of your pom.xml:

<dependencies>
    <dependency>
        <groupId>com.logistics</groupId>
        <artifactId>shipping-api</artifactId>
        <version>3.1</version>
    </dependency>
    <dependency>
        <groupId>com.finance</groupId>
        <artifactId>tax-calculator</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

The output of mvn dependency:tree:

[INFO] com.example:order-processor:jar:1.0-SNAPSHOT
[INFO] +- com.logistics:shipping-api:jar:3.1:compile
[INFO] |  \- com.google.guava:guava:jar:31.0-jre:compile
[INFO] \- com.finance:tax-calculator:jar:1.2:compile
[INFO]    \- com.google.guava:guava:jar:18.0:compile

Your Task:

  1. Identify which dependency is pulling in the outdated version of Guava.
  2. Modify the pom.xml to exclude the outdated Guava version from that specific dependency.
  3. Alternatively, implement a dependencyManagement block to force the use of Guava 31.0-jre across the entire project.

Submit the corrected pom.xml snippet that ensures Guava 31.0-jre is the version used at runtime.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.