3

Is it possible to retrieve the version of a specific maven dependency at runtime?

E.g. for the pom.xml:

<dependency>
    <groupId>foo.bar</groupId>
    <artifactId>foobar</artifactId>
    <version>1.0</version>
</dependency>

I would like to retrieve the version 1.0 of a specific dependency with artifact ID foobar.

ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
T A
  • 1,677
  • 4
  • 21
  • 29
  • 1
    https://stackoverflow.com/questions/21724145/get-jar-version-in-runtime – Mr. Arbitrary Nov 20 '19 at 14:42
  • I think this is what you are looking for : https://stackoverflow.com/a/10295470/4597596 – Ganesh chaitanya Nov 20 '19 at 14:43
  • @A.S.H thanks for the hint, but I don't think the question is about reading from the pom – T A Nov 20 '19 at 14:50
  • @GaneshchaitanyaI I do not want to specify dependencies but to read the dependency version at runtime – T A Nov 20 '19 at 14:51
  • @T A You're right, my apologies. I misunderstood your question. Please try the answer below by Arvind Kumar Avinash. – Mr. Arbitrary Nov 20 '19 at 14:54
  • 1
    By "at runtime", you mean that you have some application which relies on the foo.bar.foobar dependency and in that application you want to be able to print out the version that the Maven foo.bar.foobar dependency had at build time? – Gimby Nov 20 '19 at 15:04
  • @Gimby yes, exactly. – T A Nov 20 '19 at 15:05
  • See https://stackoverflow.com/questions/24306534/get-a-jar-file-version-number and https://stackoverflow.com/questions/20994766/jar-manifest-file-difference-between-specification-and-implementation. – VGR Nov 20 '19 at 15:08
  • Why do you want to read from pom.xml? Wouldn’t you rather have the actual version of the library at runtime, as specified in the manifest or module descriptor? – VGR Nov 20 '19 at 15:10
  • @VGR I expected it to be easier to extract it from the pom. My dependencies do not change after the build, so the contents of the pom should be sufficient. I have tried the method `MyClass.class.getPackage().getImplementationVersion()` from the answers you referred, those will return `null` to me though for my test case. – T A Nov 20 '19 at 15:14
  • The question is: Why do you need the information and for what purpose? – khmarbaise Nov 20 '19 at 15:48
  • Is it only that one dependency, or do you want to be able to list the version of any dependency that will be part of the deployable's runtime classpath? Which would also include transitive dependencies, not specifically listed in the pom... – Gimby Nov 20 '19 at 16:10
  • @Gimby No transitive dependencies, only one specific dependency and I only need the version number, no other metadata is required. – T A Nov 20 '19 at 16:16
  • Is that dependency in your own control? If so, I would "solve" it by during the build of that dependency to output a text file with the version in it, which you can simply read from the classpath then. – Gimby Nov 20 '19 at 16:19
  • @khmarbaise I have an independently versioned type system and need to track different documents versioned by that system when the are created. I could manually adjust the version number each time the type system changed, but that would add a lot of overhead and I'd like to avoid this by reading out the version automatically. – T A Nov 21 '19 at 19:57
  • @Gimby Not directly, but I'd like to do something similar in the future by just having it added as a field such that it reads it from its own pom. Right now I can't though, so this is more of a workaround. – T A Nov 21 '19 at 20:01
  • To be honest I don't understand why you need a version of a dependency? Usually you should version them based on semantical version...but nevertheless you could either read the maven information from the classpath (if you have and not module path) or you could register a SPI within each of the dependencies which has a defined interface to read that information ...? – khmarbaise Nov 22 '19 at 12:42
  • @khmarbaise They are versioned independently which is out of my control right now. I don't mean to offend but wether or not this makes sense (while I do understand your concerns, really) is out of the scope of my question. – T A Nov 22 '19 at 13:38
  • So you have only the choice to read the maven descriptor including the pom.xml / pom.properties within the jar file (on classpath)... – khmarbaise Nov 22 '19 at 14:29

3 Answers3

2

The most problematic part here is to find a JAR by its name. Unfortunately, there is no 100% reliable way for this in Java. To get a JAR by name, you need to scan classpath of the running application which may not always be present (e.g. because custom class loaders are used or module path is used instead of classpath).

But let's assume you are not using any fancy features like custom class loaders and you got your classpath which contains all your Maven dependencies. What do you need to do now? I'll try to describe a rough algorithm:

  • Retrieve paths of all JAR files from your classpath.
  • Scan each JAR file and find the file pom.properties. It's located in META-INF/maven/{groupId}/{artifactId}.
  • Find the value of the version property in pom.properties.

Again, this solution will not be completely reliable. You have to decide: do you really need the version information and for what purposes?

ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
1

Are you looking at jars you created yourself? Or third-party libaries? I have published a lightweight solution for the former. So if the packaging of the jars you are looking for at runtime is in your own hands, go ahead and give it a try ;-). It may be a little more elegant than having to read XML or properties from jar files.

The idea

  1. use a Java service loader approach to be able to add as many components/artifacts later, which can contribute their own versions at runtime. Create a very lightweight library with just a few lines of code to read, find, filter and sort all of the artifact versions on the classpath.
  2. Create a maven source code generator plugin that generates the service implementation for each of the modules at compile time, package a very simple service in each of the jars.

The solution

Part one of the solution is the artifact-version-service library, which can be found on github and MavenCentral now. It covers the service definition and a few ways to get the artifact versions at runtime.

Part two is the artifact-version-maven-plugin, which can also be found on github and MavenCentral. It is used to have a hassle-free generator implementing the service definition for each of the artifacts.

Examples

Fetching all modules with coordinates

No more reading jar manifests, just a simple method call:

// iterate list of artifact dependencies
for (Artifact artifact : ArtifactVersionCollector.collectArtifacts()) {
    // print simple artifact string example
    System.out.println("artifact = " + artifact);
}

A sorted set of artifacts is returned. To modify the sorting order, provide a custom comparator:

new ArtifactVersionCollector(Comparator.comparing(Artifact::getVersion)).collect();

This way the list of artifacts is returned sorted by version numbers.

Find a specific artifact

ArtifactVersionCollector.findArtifact("foo.bar", "foobar");

Fetches the version details for a specific artifact.

Find artifacts with matching groupId(s)

Find all artifacts with groupId foo.bar (exact match):

ArtifactVersionCollector.findArtifactsByGroupId("foo.bar", true);

Find all artifacts where groupId starts with foo.bar:

ArtifactVersionCollector.findArtifactsByGroupId("foo.bar", false);

Sort result by version number:

new ArtifactVersionCollector(Comparator.comparing(Artifact::getVersion)).artifactsByGroupId("foo.", false);

Implement custom actions on list of artifacts

By supplying a lambda, the very first example could be implemented like this:

ArtifactVersionCollector.iterateArtifacts(a -> {
    System.out.println(a);
    return false;
});

Installation

Add these two tags to all pom.xml files, or maybe to a company master pom somewhere:

<build>
  <plugins>
    <plugin>
      <groupId>de.westemeyer</groupId>
      <artifactId>artifact-version-maven-plugin</artifactId>
      <version>1.1.0</version>
      <executions>
        <execution>
          <goals>
            <goal>generate-service</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

<dependencies>
  <dependency>
    <groupId>de.westemeyer</groupId>
    <artifactId>artifact-version-service</artifactId>
    <version>1.1.0</version>
  </dependency>
</dependencies>
-2

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>WDProject</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>WDProject</name>
    <url>http://www.nosite.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>

        <selenium.version>3.141.59</selenium.version>
        <webdrivermanager.version>3.7.1</webdrivermanager.version>

    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.github.bonigarcia/webdrivermanager -->
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>${webdrivermanager.version}</version>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-api</artifactId>
            <version>${selenium.version}</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-server</artifactId>
            <version>${selenium.version}</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-remote-driver</artifactId>
            <version>${selenium.version}</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>net.lingala.zip4j</groupId>
            <artifactId>zip4j</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-model</artifactId>
            <version>3.3.9</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
                <plugin>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.7.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-project-info-reports-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

Although what you want is to search for a specific dependency, the below code retrieves all the details of the dependencies available in pom file. You just need to create a wrapper method to retrieve details of specific dependencies.


import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

import java.io.FileReader;
import java.io.IOException;


class SampleMavenDependencyReader {

    public static void main(String[] args) throws IOException, XmlPullParserException {
        MavenXpp3Reader reader = new MavenXpp3Reader();
        Model model = reader.read(new FileReader("/Users/nonadmin/bins/projects/IdeaProjects/WDProject/pom.xml"));

        for (int i = 0; i < model.getDependencies().size(); i++) {
            System.out.println(model.getDependencies().get(i));
        }
    }
}

OUTPUT

Dependency {groupId=junit, artifactId=junit, version=4.11, type=jar}
Dependency {groupId=io.github.bonigarcia, artifactId=webdrivermanager, version=${webdrivermanager.version}, type=jar}
Dependency {groupId=org.seleniumhq.selenium, artifactId=selenium-api, version=${selenium.version}, type=jar}
Dependency {groupId=org.seleniumhq.selenium, artifactId=selenium-server, version=${selenium.version}, type=jar}
Dependency {groupId=org.seleniumhq.selenium, artifactId=selenium-remote-driver, version=${selenium.version}, type=jar}
Dependency {groupId=org.seleniumhq.selenium, artifactId=selenium-java, version=${selenium.version}, type=jar}
Dependency {groupId=net.lingala.zip4j, artifactId=zip4j, version=2.2.4, type=jar}
Dependency {groupId=org.apache.maven, artifactId=maven-model, version=3.3.9, type=jar}~~~

forkdbloke
  • 1,505
  • 2
  • 12
  • 29