Dependency Conflict Resolution

About

A dependency conflict occurs in Maven when multiple dependencies in a project bring different versions of the same library (transitive dependencies), leading to:

  • Version Mismatches → Different versions of the same dependency in the classpath.

  • Compilation/Runtime Issues → Unexpected behavior due to incompatible versions.

  • ClassNotFoundException / NoSuchMethodError → When an older version is used instead of the expected one.

Maven resolves these conflicts automatically, but understanding the resolution mechanism helps avoid issues.

How Do Dependency Conflicts Occur?

Consider a project with dependencies:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.1</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

What Happens Internally?

  • spring-boot-starter-web also depends on log4j-core, but at version 2.14.1.

  • Now, two versions (2.17.1 & 2.14.1) exist in the dependency tree.

  • Maven must decide which version to use.

What Happens If Two Versions Are Declared?

Example: Conflicting Dependencies

Maven resolves this conflict automatically, choosing only one version based on:

  1. Direct Dependency Preference – If one version is declared explicitly, it is preferred.

  2. Nearest-Wins Strategy – If both are transitive dependencies, Maven picks the closest version.

To check which version is used:

Maven’s Conflict Resolution Strategy

1. Nearest-Wins Strategy (Dependency Mediation)

  • Maven chooses the nearest dependency version in the dependency tree.

  • If a dependency appears multiple times, the closest one to the project wins.

Example: Nearest-Wins in Action

If lib-B internally depends on lib-A:1.0.0, but the project declares lib-A:1.2.0, Maven will use 1.2.0 because it is closer to the project.

Issue: This may cause incompatibilities if the newer version is not backward compatible.

2. Dependency Tree Analysis

Use the following command to check all dependencies and their versions:

Example output:

Solution: Manually enforce log4j-core:2.17.1 to resolve the conflict.

3. Forcing a Specific Version (Dependency Management)

If Maven picks an older or undesired version, explicitly override it using <dependencyManagement>:

This ensures all modules in a multi-module project use the same version.

4. Using Exclusions to Remove Unwanted Dependencies

If a dependency is causing conflicts indirectly, we can exclude it:

This removes log4j-core from spring-boot-starter-web.

5. Using a BOM (Bill of Materials)

BOMs standardize versions across projects:

It ensures consistent versions across modules.

Can We Force Two Versions in the Same Project?

Not directly in the same classpath! But some workarounds exist:

Solution 1: Shading (Relocating Dependencies)

  • Used in fat JARs (e.g., with Maven Shade Plugin) to bundle different versions under separate namespaces.

  • Example: If two different libraries require different jackson-databind versions, we can relocate one version.

This moves jackson-databind:2.13.3 under shaded.com.fasterxml.jackson, avoiding conflicts.

Solution 2: Isolating Dependencies in Different Classloaders

  • Used in OSGi, Java Modules (JPMS), or custom classloading strategies.

  • Example: Spring Boot’s parent-first and child-first classloading strategies.

Last updated