199

I have noticed that in a Maven artifact's JAR, the project.version attribute is included in two files:

META-INF/maven/${groupId}/${artifactId}/pom.properties
META-INF/maven/${groupId}/${artifactId}/pom.xml

Is there a recommended way to read this version at runtime?

bluish
  • 26,356
  • 27
  • 122
  • 180
Armand
  • 23,463
  • 20
  • 90
  • 119

13 Answers13

293

You should not need to access Maven-specific files to get the version information of any given library/class.

You can simply use getClass().getPackage().getImplementationVersion() to get the version information that is stored in a .jar-files MANIFEST.MF. Unfortunately Maven does not write the correct information to the manifest as well by default!

Instead one has to modify the <archive> configuration element of the maven-jar-plugin to set addDefaultImplementationEntries and addDefaultSpecificationEntries to true, like this:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>                   
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
</plugin>

Ideally this configuration should be put into the company pom or another base-pom.

Detailed documentation of the <archive> element can be found in the Maven Archive documentation.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • 7
    sadly not every classloader does seem to load these properties from the manifest file (I remember having problems with Tomcat in exactly this case). – dwegener Dec 15 '12 at 14:17
  • @avithan: really? I never had a problem with Tomcat with this approach. Also, I think a classloader that ignores the manifest is probably not conforming. – Joachim Sauer Dec 16 '12 at 12:18
  • @JoachimSauer ok, I was wrong. Currently it seems it works great on HotSpot but does not work reliable on OpenJDK. I will report back when I get detailed information – dwegener Jan 09 '13 at 16:56
  • @avithan this is relevant to me (and I have not seen what you report) - have you gotten detailed information yet? – Thorbjørn Ravn Andersen Mar 13 '13 at 09:42
  • 5
    Unfortunately this doesn't work if the project is run from Eclipse or using "mvn exec:java". – Jaan Mar 20 '14 at 08:26
  • I found a code [this](http://stackoverflow.com/questions/2712970/get-maven-artifact-version-at-runtime) that no change Maven config. – Wendel Jul 08 '16 at 14:31
  • One major problem with this approach (besides needing to run the build first) is that while you can get the `project.version` you can't get the `artifactId` as `project.name` isn't always the artifactId. – Adam Gent Jan 25 '17 at 18:17
  • maven-assembly-plugin supports many of the same options as maven-jar-plugin, but silently ignores these. – Kevin Krumwiede May 27 '17 at 20:23
  • With the advent of Java 9+ and Jigsaw, this doesn't work anymore. – bluemind Jun 26 '19 at 09:27
  • This also only works if you control the dependencies you're investigating - i.e. being able to change the jar plugin config. – Antony Stubbs Nov 12 '21 at 16:24
  • Any solution if I want the actual Maven profile name – Sonn Jan 19 '23 at 21:33
  • @Sonn: that should probably be posted as a separate question. It doesn't fit this question and the comments are not the right place to discuss these things. – Joachim Sauer Jan 20 '23 at 09:11
81

To follow up the answer above, for a .war artifact, I found I had to apply the equivalent configuration to maven-war-plugin, rather than maven-jar-plugin:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.1</version>
    <configuration>
        <archive>                   
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
</plugin>

This added the version information to MANIFEST.MF in the project's .jar (included in WEB-INF/lib of the .war)

thomas.mc.work
  • 6,404
  • 2
  • 26
  • 41
Rob
  • 982
  • 8
  • 14
31

Here's a method for getting the version from the pom.properties, falling back to getting it from the manifest

public synchronized String getVersion() {
    String version = null;

    // try to load from maven properties first
    try {
        Properties p = new Properties();
        InputStream is = getClass().getResourceAsStream("/META-INF/maven/com.my.group/my-artefact/pom.properties");
        if (is != null) {
            p.load(is);
            version = p.getProperty("version", "");
        }
    } catch (Exception e) {
        // ignore
    }

    // fallback to using Java API
    if (version == null) {
        Package aPackage = getClass().getPackage();
        if (aPackage != null) {
            version = aPackage.getImplementationVersion();
            if (version == null) {
                version = aPackage.getSpecificationVersion();
            }
        }
    }

    if (version == null) {
        // we could not compute the version so use a blank
        version = "";
    }

    return version;
} 
The Alchemist
  • 3,397
  • 21
  • 22
mysomic
  • 1,546
  • 3
  • 21
  • 34
  • 2
    Put this in a static initializer block. – opyate Feb 04 '13 at 15:48
  • 1
    Good advice. Although, if you're using this in a servlet (or .jsp), be sure to use getServletContext().getResourceAsStream instead of getClass().getResourceAsStream – Sandman Nov 18 '13 at 12:34
  • 4
    This only works when the application is run from the jar. If run from exec-maven-plugin (e.g. Netbeans) the resource is null. – Leif Gruenwoldt Oct 27 '14 at 14:46
  • This code will be part of my main class defaults! Thanks!! – Wendel Jul 08 '16 at 14:29
  • I used this with Will's answer for a straight forward and easy to maintain option. – javydreamercsw May 30 '17 at 20:00
  • Since we are talking about WAR, I had to replace the line: InputStream is = getClass().getResourceAsStream("/META-INF/maven/com.my.group/my-artifact/pom.properties"); with: InputStream is = request.getServletContext().getResourceAsStream("/META-INF/maven/com.my.group/my-artifact/pom.properties"); See also https://stackoverflow.com/questions/2161054/where-to-place-and-how-to-read-configuration-resource-files-in-servlet-based-app – John Mikic Oct 08 '19 at 20:56
12

If you happen to use Spring Boot you can make use of the BuildProperties class.

Take the following snippet from our OpenAPI configuration class as an example:

@Configuration
@RequiredArgsConstructor // <- lombok
public class OpenApi {

    private final BuildProperties buildProperties; // <- you can also autowire it

    @Bean
    public OpenAPI yourBeautifulAPI() {
        return new OpenAPI().info(new Info()
            .title(buildProperties.getName())
            .description("The description")
            .version(buildProperties.getVersion())
            .license(new License().name("Your company")));
    }
}
darefilz
  • 641
  • 6
  • 19
  • 1
    This is exactly the use case that made me look for solutions to runtime Maven details, how convenient! Maybe it should be addressed in a different question, but it's handy nonetheless. Thanks! – Xerz Feb 08 '22 at 21:06
  • You may need to add build info do your pom.xml, like mentioned in @chris-sim's answer – Guilherme Taffarel Bergamin Jul 31 '23 at 21:18
9

I am using maven-assembly-plugin for my maven packaging. The usage of Apache Maven Archiver in Joachim Sauer's answer could also work:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
    <executions>
        <execution .../>
    </executions>
</plugin>

Because archiever is one of maven shared components, it could be used by multiple maven building plugins, which could also have conflict if two or more plugins introduced, including archive configuration inside.

Community
  • 1
  • 1
千木郷
  • 1,595
  • 2
  • 19
  • 30
7

I know it's a very late answer but I'd like to share what I did as per this link:

I added the below code to the pom.xml:

        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>build-info</id>
                    <goals>
                        <goal>build-info</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

And this Advice Controller in order to get the version as model attribute:

import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;

@ControllerAdvice
public class CommonControllerAdvice
{
       @Autowired
       BuildProperties buildProperties;
    
       @ModelAttribute("version")
       public String getVersion() throws IOException
       {
          String version = buildProperties.getVersion();
          return version;
       }
    }
Chris Sim
  • 4,054
  • 4
  • 29
  • 36
3

I spent some time on the two main approaches here and they didn't work-out for me. I am using Netbeans for the builds, may be there's more going on there. I had some errors and warnings from Maven 3 with some constructs, but I think those were easy to correct. No biggie.

I did find an answer that looks maintainable and simple to implement in this article on DZone:

I already have a resources/config sub-folder, and I named my file: app.properties, to better reflect the kind of stuff we may keep there (like a support URL, etc.).

The only caveat is that Netbeans gives a warning that the IDE needs filtering off. Not sure where/how. It has no effect at this point. Perhaps there's a work around for that if I need to cross that bridge. Best of luck.

will
  • 4,799
  • 8
  • 54
  • 90
3

To get this running in Eclipse, as well as in a Maven build, you should add the addDefaultImplementationEntries and addDefaultSpecificationEntries pom entries as described in other replies, then use the following code:

public synchronized static final String getVersion() {
    // Try to get version number from pom.xml (available in Eclipse)
    try {
        String className = getClass().getName();
        String classfileName = "/" + className.replace('.', '/') + ".class";
        URL classfileResource = getClass().getResource(classfileName);
        if (classfileResource != null) {
            Path absolutePackagePath = Paths.get(classfileResource.toURI())
                    .getParent();
            int packagePathSegments = className.length()
                    - className.replace(".", "").length();
            // Remove package segments from path, plus two more levels
            // for "target/classes", which is the standard location for
            // classes in Eclipse.
            Path path = absolutePackagePath;
            for (int i = 0, segmentsToRemove = packagePathSegments + 2;
                    i < segmentsToRemove; i++) {
                path = path.getParent();
            }
            Path pom = path.resolve("pom.xml");
            try (InputStream is = Files.newInputStream(pom)) {
                Document doc = DocumentBuilderFactory.newInstance()
                        .newDocumentBuilder().parse(is);
                doc.getDocumentElement().normalize();
                String version = (String) XPathFactory.newInstance()
                        .newXPath().compile("/project/version")
                        .evaluate(doc, XPathConstants.STRING);
                if (version != null) {
                    version = version.trim();
                    if (!version.isEmpty()) {
                        return version;
                    }
                }
            }
        }
    } catch (Exception e) {
        // Ignore
    }

    // Try to get version number from maven properties in jar's META-INF
    try (InputStream is = getClass()
        .getResourceAsStream("/META-INF/maven/" + MAVEN_PACKAGE + "/"
                + MAVEN_ARTIFACT + "/pom.properties")) {
        if (is != null) {
            Properties p = new Properties();
            p.load(is);
            String version = p.getProperty("version", "").trim();
            if (!version.isEmpty()) {
                return version;
            }
        }
    } catch (Exception e) {
        // Ignore
    }

    // Fallback to using Java API to get version from MANIFEST.MF
    String version = null;
    Package pkg = getClass().getPackage();
    if (pkg != null) {
        version = pkg.getImplementationVersion();
        if (version == null) {
            version = pkg.getSpecificationVersion();
        }
    }
    version = version == null ? "" : version.trim();
    return version.isEmpty() ? "unknown" : version;
}

If your Java build puts target classes somewhere other than "target/classes", then you may need to adjust the value of segmentsToRemove.

Luke Hutchison
  • 8,186
  • 2
  • 45
  • 40
  • You know if this is for unit tests you can just `System.getProperty("user.dir")/pom.xml`. I'm fairly sure it will for other things as well except maybe not for WTP. – Adam Gent Jan 25 '17 at 18:14
  • That will only work if your project is in a directory -- if you're running a project based in jarfiles, your solution won't work. You need to use `.getResource()` or `.getResourceAsStream()`. – Luke Hutchison Jan 26 '17 at 22:01
  • Yes I was assuming you have already checked the jar (ala getResource). That is first you check with getResource if that fails then the project hasn't been built into a jar yet which means you are either running it from Eclipse or Maven which means `System.getProperty("user.dir")/pom.xml. The only issue is this pom file isn't the true effective pom (that is some properties will not be expanded) yet but neither is the one you are getting with the Eclipse way. – Adam Gent Jan 27 '17 at 12:25
3

A simple solution which is Maven compatible and works for any (thus also third party) class:

    private static Optional<String> getVersionFromManifest(Class<?> clazz) {
        try {
            File file = new File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
            if (file.isFile()) {
                JarFile jarFile = new JarFile(file);
                Manifest manifest = jarFile.getManifest();
                Attributes attributes = manifest.getMainAttributes();
                final String version = attributes.getValue("Bundle-Version");
                return Optional.of(version);
            }
        } catch (Exception e) {
            // ignore
        }
        return Optional.empty();
    }

Here’s a version without Optional<> that just returns null if not present (for quick debugging/dumping):

    private static String getVersionFromManifest(Class<?> clazz) {
        try {
            File file = new File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
            if (file.isFile()) {
                JarFile jarFile = new JarFile(file);
                Manifest manifest = jarFile.getManifest();
                Attributes attributes = manifest.getMainAttributes();
                return attributes.getValue("Bundle-Version");
            }
        } catch (Exception e) {
            // ignore
        }
        return null;
    }
mirabilos
  • 5,123
  • 2
  • 46
  • 72
rdehuyss
  • 734
  • 8
  • 14
2

On my spring boot application, the solution from the accepted answer worked until I recently updated my jdk to version 12. Tried all the other answers as well and couldn't get that to work.

At that point, I added the below line to the first class of my spring boot application, just after the annotation @SpringBootApplication

@PropertySources({ 
        @PropertySource("/META-INF/maven/com.my.group/my-artefact/pom.properties")
})

Later I use the below to get the value from the properties file in whichever class I want to use its value and appVersion gets the project version to me:

@Value("${version}")
private String appVersion;

Hope that helps someone.

Reema
  • 587
  • 3
  • 12
  • 37
  • How to do the same with multiple pom files? I want to load version from multiple pom files. – THM Feb 28 '20 at 10:18
2

The most graceful solutions I've found is that one from J.Chomel: link

Doesn't require any hacks with properties. To avoid issues with broken link in a future I'll duplicate it here:

YourClass.class.getPackage().getImplementationVersion();

And (if you don't have Manifest file in your jar/war yet, for me Intellij Idea's Maven already included them) you will require also a small change in pom.xml:

<build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
     ...
      <plugin>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.2.2</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
                <archive>
                    <manifest>
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    ...
  • This works well when you’re able to add these entries, but not when you want the version of existing artefacts that **do not** define them (Mockito, for example, defines `Bundle-Version` but not `Implementation-Version` ☹). – mirabilos Dec 17 '21 at 17:43
  • Do you have any solution to get the running Maven profile I'd? – Sonn Jan 19 '23 at 21:38
0

Tried all the answers above but nothing worked for me:

  • I did not use Spring
  • Managed to put Version inside of manifest, but someClass.class.getPackage().getImplementationVersion() returned null

However version was appended to the jar file name so I was able to find a jar file using:

new File(ClassLoader.getSystemResource("").toURI()).getParentFile();

and then extract it from the file name.

-1

Java 8 variant for EJB in war file with maven project. Tested on EAP 7.0.

@Log4j // lombok annotation
@Startup
@Singleton
public class ApplicationLogic {

    public static final String DEVELOPMENT_APPLICATION_NAME = "application";

    public static final String DEVELOPMENT_GROUP_NAME = "com.group";

    private static final String POM_PROPERTIES_LOCATION = "/META-INF/maven/" + DEVELOPMENT_GROUP_NAME + "/" + DEVELOPMENT_APPLICATION_NAME + "/pom.properties";

    // In case no pom.properties file was generated or wrong location is configured, no pom.properties loading is done; otherwise VERSION will be assigned later
    public static String VERSION = "No pom.properties file present in folder " + POM_PROPERTIES_LOCATION;

    private static final String VERSION_ERROR = "Version could not be determinated";

    {    
        Optional.ofNullable(getClass().getResourceAsStream(POM_PROPERTIES_LOCATION)).ifPresent(p -> {

            Properties properties = new Properties();

            try {

                properties.load(p);

                VERSION = properties.getProperty("version", VERSION_ERROR);

            } catch (Exception e) {

                VERSION = VERSION_ERROR;

                log.fatal("Unexpected error occured during loading process of pom.properties file in META-INF folder!");
            }
        });
    }
}
onderbewustzijn
  • 935
  • 7
  • 32