Skip to Content

What Version Is This?

How to find the version of a packaged Java library at runtime

Posted on
Photo by Janko Ferlič on Unsplash
Photo by Janko Ferlič on Unsplash

I ran into an interesting problem the other day: How can a library I maintain determine its own version programmatically at runtime? My use case was to log out the version of a library when it is used, but if you have the name, vendor, and version of a java library at runtime, you could do all sorts of things (maybe dynamically switch implementations?).

There are a few ways we could do this, and I was happy to find out that Java already has everything we need without having to write any custom code.

The code for this post is hosted in this GitHub repository, please check it out!

Potential Approaches

One way we could solve this problem is to have our build echo the current version number into a text file like VERSION.txt and package it into the jar it is building. Then, in Java, we would write a class to read this text file in, parse it, and do whatever we needed to do with the version number. This is a fairly common approach, so finding example code should not be difficult.

Another way would be to skip all that data loading and have our build dynamically generate a class file that hard codes the version. This pushes a lot more of the work into our build, but makes our runtime class something we don’t need to maintain. For this use case, I want to avoid maintaining all of the build changes needed to do this.

The Easy Way

It turns out that when we build a jar file, there is a file in there called META-INF/MANIFEST.MF. This manifest file usually contains information about when and how the jar was built. Think of it as a property file that describes the jar itself. Among other things this manifest file can declare a Main-Class attribute whose value is a fully qualified class that is packaged in the jar. This is how java -jar yourjar.jar knows to run a specific class when creating an executable jar!

If you read the Java Jar Manifest Specification, you will find a few properties that might be of some use to us:

Attribute Name Value Purpose
Implementation-Title The title of the extension implementation
Implementation-Version The version of the extension implementation
Implementation-Vendor The organization that maintains the extension implementation

(There are more attributes, I’m just focusing on the ones we care about for this post).

The best part is that these are available at runtime without writing anything to read them in or manipulate them - Java does this for us by exposing these as attributes on the java.lang.Package class! In order to print these three values out, we could package this JavaLogVersion into our jar. By calling getClass().getPackage(), Java will give us what we need!

public class JavaLogVersion {
    public void logVersion() {
        final Package logPackage = getClass().getPackage();
        System.out.printf("Application: %s, version: %s, by %s%n",
                logPackage.getImplementationTitle(),
                logPackage.getImplementationVersion(),
                logPackage.getImplementationVendor()
        );
    }
}

The one downside to this is that if we are running this code as part of a test, or not as it would be packaged in a jar, these values will all be null.

How Do We Specify These Values

All that remains is to get our preferred implementation title, version, and vendor into our jar file. Gradle and Maven both have support to customize the manifest, so we can set these to whatever we want.

// File: build.gradle.kts

tasks.jar {
    manifest {
        attributes(
            "Main-Class" to "com.ginsberg.version.Main",
            "Implementation-Title" to "Todd's Test Application",
            "Implementation-Version" to archiveVersion,
            "Implementation-Vendor" to "Todd Ginsberg"
        )
    }
}

And if we run our example:


$ java -jar build/libs/self-version-demo.jar

Application: Todd's Test Application, version: 1.0.0-SNAPSHOT, by Todd Ginsberg

I hope you found that helpful! I was happy to run into this solution as it requires very little code and lets me originate this information from my build without having to depend on another plugin or write custom code myself.