65

Is it possible to declare multiple versions of the same dependency in a Maven repo?

I need these dependencies all at once:

    <dependency>
        <groupId>org.bukkit</groupId>
        <artifactId>craftbukkit</artifactId>
        <version>1.7.9-R0.2</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.bukkit</groupId>
        <artifactId>craftbukkit</artifactId>
        <version>1.7.2-R0.3</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.bukkit</groupId>
        <artifactId>craftbukkit</artifactId>
        <version>1.6.4-R2.0</version>
        <scope>compile</scope>
    </dependency>

Because each of them contains a different package I care about:

org.bukkit.craftbukkit.v1_6_R3

org.bukkit.craftbukkit.v1_7_R1

org.bukkit.craftbukkit.v1_7_R3

If I declare dependencies as shown in the first snippet, only the last one will take effect. Is there any way to achieve this in Maven?

@Edit Any workaround, maybe?

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
wassup
  • 2,333
  • 2
  • 21
  • 30
  • 4
    Why do you need to do this? If Maven allowed this behaviour you'd have an unpredictable classpath with multiple copies of the same classes. Java doesn't version classes at run-time, this means Maven's restriction is a good thing! – Mark O'Connor Jul 25 '14 at 19:23
  • 2
    I used to have many versions of the same library included in the classpath before I started using Maven, and I didn't have any problems. I need this to provide backward compatibility to Bukkit, which is a gaming server for Minecraft that I ran my plugins on. – wassup Jul 25 '14 at 19:29
  • It may have been going without any problems but it is definitely a risky thing to do. Backward compatibility should be addressed by the newer version itself at a minimum. – M A Jul 25 '14 at 19:37
  • 1
    Well, I guess you have never written a plugin for Bukkit :) – wassup Jul 25 '14 at 19:39
  • People have servers running on different versions of Bukkit (beta, stable, etc.) and I need to support at least a few of them. – wassup Jul 25 '14 at 19:40
  • 3
    @wassup That makes no sense to me sorry.... If I support multiple versions of a product I use different branches within my source code control system and build each version separately. That way each has its own unique set of dependency versions. The point is at run-time there is only one copy of the classes. If you use multiple versions of the same jar, you end up with the tricky problem that you don't really know which one the java classloader is using. This is the problem Maven prevents by insisting on one version. – Mark O'Connor Jul 26 '14 at 16:37
  • 1
    Hello Mark - I can give you an use case where multiple versions are needed. At our company we have an group of applications that are being run on Apache Servicemix. Servicemix is interesting in that everything is configured with feature files we call out which bundles that need to be installed. When servicemix starts up it can pull those bundles straight from the maven repository - this works great until we get to our certification and production environments where we are not allowed to connect to our companies maven repo. – Scott Conway Jan 12 '16 at 16:38
  • 1
    continued from above. servicemix does allow us to point to a directory and look at that as a maven repo so we use the maven-dependency-plugin to gather up all the dependencies into a directory and the maven-assembly-plugin to tar it up which we untar into our servicemix as part of the deployment process. the issue is that some of our features need different versions of a dependency than other features (some of those installed by servicemix itself) so we either have to upgrade/downgrade all our apps to the same version or hand copy the extra versions into the directory to be tarred up. – Scott Conway Jan 12 '16 at 16:41

6 Answers6

40

No. Maven will only resolve one dependency in your module and will omit the other versions to avoid any conflict. Even if multiple versions of the same dependency are used in the whole dependency hierarchy, Maven will pick one version using the "nearest in the dependency tree" strategy.

It is possible to specify different dependency versions using different profiles. For each version of Bukkit a profile can be defined and activated. Still if you activate more than one profile, only one version would be used.

<profiles>
    <profile>
        <id>Bukkit_1_7_9_R02</id>
        <activation>
            ...
        </activation>
        <dependencies>
            <dependency>
                <groupId>org.bukkit</groupId>
                <artifactId>craftbukkit</artifactId>
                <version>1.7.9-R0.2</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    </profile>
    <profile>
        <id>Bukkit_1_7_2_R03</id>
        <activation>
            ...
        </activation>
        <dependencies>
            <dependency>
                <groupId>org.bukkit</groupId>
                <artifactId>craftbukkit</artifactId>
                <version>1.7.2-R0.3</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    </profile>
    ...
</profiles>
M A
  • 71,713
  • 13
  • 134
  • 174
  • Maybe it is possible to enable a profile for a specific package/set of files? – wassup Jul 26 '14 at 00:09
  • 1
    More information about resolving the used dependency can be found here: [Dependency Mechanism - Transitive Dependencies](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Transitive_Dependencies) – JaXt0r Apr 09 '17 at 09:59
30

Try to cheat maven:

<dependency>
    <groupId>org.bukkit</groupId>
    <artifactId>craftbukkit</artifactId>
    <version>1.7.9-R0.2</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.bukkit.</groupId>
    <artifactId>craftbukkit</artifactId>
    <version>1.7.2-R0.3</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.bukkit..</groupId>
    <artifactId>craftbukkit</artifactId>
    <version>1.6.4-R2.0</version>
    <scope>compile</scope>
</dependency>
Dmitri
  • 373
  • 3
  • 3
  • 3
    @Dmitri Can you please explain how is this cheating maven and why did you add an extra dot in groupId? What will be the end result? What should one expect? – The-Rohan D. Shah Aug 11 '21 at 04:41
  • 1
    Quite funny that this works. I guess it works because according to the [maven docs](https://maven.apache.org/pom.html#maven-coordinates) the dots are translated to directory separators within the repository. And the OSes handle multiple slashes in file system paths well. e.g. Windows resolves a path like `C:\path\\with\\\slashes.txt` to the file `C:\path\with\slashes.txt`. – Danielku15 Aug 16 '21 at 12:29
  • 1
    How does this work!?! Thanks lol! – Anston Sorensen Oct 28 '21 at 20:55
  • 3
    This is really horrible. Just don't do this. There are so much cleaner solutions. – J Fabian Meier Mar 30 '22 at 09:25
  • I don't get it, why does that resolve the OP's issue ? The package name in the import still clashes doesn't it? – jsalvas Feb 10 '23 at 10:01
14

Here is the relevant part of the Maven documentation, which explains how Maven chooses the version of a dependency when there is more than one possibility:

Dependency mediation - this determines what version of an artifact will be chosen 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. "nearest definition" means that the version used will be the closest one to your project in the tree of dependencies. Consider this tree of dependencies:

A
├── B
│   └── C
│       └── D 2.0
└── E
    └── D 1.0

In text, 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, as shown here:

A
├── B
│   └── C
│       └── D 2.0
├── E
│   └── D 1.0
│
└── D 2.0
Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • what will be the scenario if its like A-> B -> D-> 1.1.0 and A-> C-> D-> 2.1.0 ? In short what will be the case if differnt versions are the same level, i.e there are multipe closest versions? – Deekshith Anand May 19 '22 at 08:44
  • 1
    @DeekshithAnand As per the maven documentation "Note that if two dependency versions are at the same depth in the dependency tree, the first declaration wins." – Faisal Feroz Aug 24 '23 at 12:34
6

No, you can't depend 2 versions of the same artifact, normally.
But you can include them so they end up in the resulting application.

That requirement is sometimes valid - for instance, when the maintenance of that library is poor, and they rename some packages and release that as a minor version of the same artifact. Then, other projects have it as a 3rd party dependency and need the same classes under different FQCN.

For such cases, you can for instance use the maven-shade-plugin.

  • Create a maven project with a single dependency, one of the versions you need.
  • Add the shade plugin and let it create a shaded jar. It will basically re-wrap the classes under a different artifact G:A:V.
  • Do this for all versions you need.
  • You may use classifier to differentiate the shaded versions.
  • In your project, depend on these artifacts.
  • Finally, exclude the original dependencies, give them the scope "provided".

You can use different variations of the same, which, in the end, will put those classes to your classpath. For instance, use the dependency:copy-dependency plugin/goal, and install that jar to your local repo during the build. Or unzip the classes right into your ${project.build.outputDirectory} (target/classes).

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
0

I'm still pretty new, but something I've been running into with axis2 is that the individual modules sometimes require an earlier version because of a change they made to the classes, so the top level dependency only catches about half of them. The rest I've been having to individually correct the poms for for explicit dependencies to a different version.

Maybe this approach would work for you as well? Having the plugin have modular components for their specific dependencies.

  • If I understand the situation correctly, I think you can specify it once in your own pom under the `dependencyManagement` section. This will force that version to be pulled into your project/assembly. – Vivek Chavda Jun 23 '17 at 13:57
0

This is how I got around it. FYI: In my case I was building a RPM.

I used the maven-dependency-plugin to copy the older dependency that gets ignored to a folder in the build directory and then copied that file to the staging area so that it's included in the RPM.

Here's the code to copy the older dependency:

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.1.1</version>
    <executions>
      <execution>
        <id>copy-dependencies</id>
        <phase>prepare-package</phase>
        <goals>
          <goal>copy</goal>
        </goals>
        <configuration>
          <artifactItems>
            <artifactItem>
              <groupId>some.package.group.id</groupId>
              <artifactId>someartifact-id</artifactId>
              <version>1.2.3</version>
              <overWrite>false</overWrite>
              <outputDirectory>${project.build.directory}/older-dependencies</outputDirectory>
            </artifactItem>
          </artifactItems>
        </configuration>
      </execution>
    </executions>
  </plugin>

And then later on during the building of my RPM I included this scriptlet in the configuration section of my rpm-maven-plugin. This will copy the file to the staging area for the RPM:

<installScriptlet>
  <script>cp ${project.build.directory}/older-dependencies/* ${project.build.directory}/rpm/${artifactId}/buildroot${installBase}/</script>
</installScriptlet>
schmudu
  • 2,111
  • 1
  • 21
  • 30