reading MANIFEST.MF file from jar file using JAVA
Asked Answered
V

10

41

Is there any way I can read the content of a jar file. I want to read the manifest file in order to find the creator of the jar file and version. Is there any way to achieve it?

Vertical answered 23/9, 2010 at 9:39 Comment(0)
S
57

Next code should help:

JarInputStream jarStream = new JarInputStream(stream);
Manifest mf = jarStream.getManifest();

Exception handling is left for you :)

Saucy answered 23/9, 2010 at 9:46 Comment(1)
uh... what is stream here?Joyous
G
48

I would suggest to make following:

Package aPackage = MyClassName.class.getPackage();
String implementationVersion = aPackage.getImplementationVersion();
String implementationVendor = aPackage.getImplementationVendor();

Where MyClassName can be any class from your application written by you.

Grown answered 11/9, 2013 at 12:49 Comment(2)
This is the thing I need (to read Implementation-Version). Sadly, this failed to work for me on unpacked classes, though the manifest is in 'META-INF/MANIFEST.MF'.Fairhaired
This doesn't work by default unless you add addDefaultImplementationEntries to true for maven-jar-plugin so that it actually has version information in it. By default Manifest file doesn't include anything. Also @Victor, try to run mvn package and see the inside if it has proper information. Generally those version configuration is in maven-jar-plugin or maven-war-plugin, so unpackaged classes doesn't have it.Melodious
S
45

You could use something like this:

public static String getManifestInfo() {
    Enumeration resEnum;
    try {
        resEnum = Thread.currentThread().getContextClassLoader().getResources(JarFile.MANIFEST_NAME);
        while (resEnum.hasMoreElements()) {
            try {
                URL url = (URL)resEnum.nextElement();
                InputStream is = url.openStream();
                if (is != null) {
                    Manifest manifest = new Manifest(is);
                    Attributes mainAttribs = manifest.getMainAttributes();
                    String version = mainAttribs.getValue("Implementation-Version");
                    if(version != null) {
                        return version;
                    }
                }
            }
            catch (Exception e) {
                // Silently ignore wrong manifests on classpath?
            }
        }
    } catch (IOException e1) {
        // Silently ignore wrong manifests on classpath?
    }
    return null; 
}

To get the manifest attributes, you could iterate over the variable "mainAttribs" or directly retrieve your required attribute if you know the key.

This code loops through every jar on the classpath and reads the MANIFEST of each. If you know the name of the jar you may want to only look at the URL if it contains() the name of the jar you are interested in.

Stairs answered 23/9, 2010 at 9:46 Comment(3)
Excellent that this goes through all Manifests.Emileeemili
I am getting wrong Manifest with this code. It says for example: Main-Class:org.GNOME.Accessibility.AtkWrapper. But this ist not the case. Strange.Sphagnum
Iterating over the keys of manifest.getMainAttributes() or manifest.getEntries() give me no keys or values. Accessing the value directly as shown above works fine for me. Is there any background information concerning this behavior?Shinleaf
B
12

I implemented an AppVersion class according to some ideas from stackoverflow, here I just share the entire class:

import java.io.File;
import java.net.URL;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AppVersion {
  private static final Logger log = LoggerFactory.getLogger(AppVersion.class);

  private static String version;

  public static String get() {
    if (StringUtils.isBlank(version)) {
      Class<?> clazz = AppVersion.class;
      String className = clazz.getSimpleName() + ".class";
      String classPath = clazz.getResource(className).toString();
      if (!classPath.startsWith("jar")) {
        // Class not from JAR
        String relativePath = clazz.getName().replace('.', File.separatorChar) + ".class";
        String classFolder = classPath.substring(0, classPath.length() - relativePath.length() - 1);
        String manifestPath = classFolder + "/META-INF/MANIFEST.MF";
        log.debug("manifestPath={}", manifestPath);
        version = readVersionFrom(manifestPath);
      } else {
        String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";
        log.debug("manifestPath={}", manifestPath);
        version = readVersionFrom(manifestPath);
      }
    }
    return version;
  }

  private static String readVersionFrom(String manifestPath) {
    Manifest manifest = null;
    try {
      manifest = new Manifest(new URL(manifestPath).openStream());
      Attributes attrs = manifest.getMainAttributes();

      String implementationVersion = attrs.getValue("Implementation-Version");
      implementationVersion = StringUtils.replace(implementationVersion, "-SNAPSHOT", "");
      log.debug("Read Implementation-Version: {}", implementationVersion);

      String implementationBuild = attrs.getValue("Implementation-Build");
      log.debug("Read Implementation-Build: {}", implementationBuild);

      String version = implementationVersion;
      if (StringUtils.isNotBlank(implementationBuild)) {
        version = StringUtils.join(new String[] { implementationVersion, implementationBuild }, '.');
      }
      return version;
    } catch (Exception e) {
      log.error(e.getMessage(), e);
    }
    return StringUtils.EMPTY;
  }
}

Basically, this class can read version information from the manifest of its own JAR file, or the manifest in it's classes folder. And hopefully it works on different platforms, but I only tested it on Mac OS X so far.

I hope this would be useful for someone else.

Bathtub answered 24/7, 2013 at 7:46 Comment(2)
The only issue with this is the line containing: String manifestPath = StringUtils.join[...] Using File.separatorChar seems to generate an invalid URL under Windows... probably best to use "/" instead.Ulita
This worked for me, but then I found that the answer by stviper also worked, and was much simpler.Scutch
G
4

Achieve the attributes in this simple way

    public static String  getMainClasFromJarFile(String jarFilePath) throws Exception{
    // Path example: "C:\\Users\\GIGABYTE\\.m2\\repository\\domolin\\DeviceTest\\1.0-SNAPSHOT\\DeviceTest-1.0-SNAPSHOT.jar";
    JarInputStream jarStream = new JarInputStream(new FileInputStream(jarFilePath));
    Manifest mf = jarStream.getManifest();
    Attributes attributes = mf.getMainAttributes();
    // Manifest-Version: 1.0
    // Built-By: GIGABYTE
    // Created-By: Apache Maven 3.0.5
    // Build-Jdk: 1.8.0_144
    // Main-Class: domolin.devicetest.DeviceTest
    String mainClass = attributes.getValue("Main-Class");
    //String mainClass = attributes.getValue("Created-By");
    //  Output: domolin.devicetest.DeviceTest
    return mainClass;
}
Grade answered 3/2, 2019 at 12:39 Comment(0)
B
3

You can use a utility class Manifests from jcabi-manifests:

final String value = Manifests.read("My-Version");

The class will find all MANIFEST.MF files available in classpath and read the attribute you're looking for from one of them. Also, read this: http://www.yegor256.com/2014/07/03/how-to-read-manifest-mf.html

Barefoot answered 30/12, 2012 at 8:56 Comment(1)
This is great and works well. Nice that it is a library. What would make it better is if all Manifests were read. I notice the Javadoc has an entry(), which may be what I've suggested, however that method isn't appearing in the version I'm using (1.1 - the latest at this date on MvnRepository). The answer https://mcmap.net/q/385337/-reading-manifest-mf-file-from-jar-file-using-java has the solution that worked to retrieve all Manifests.Emileeemili
F
2
  1. read version;
  2. we copied MANIFEST.MF from jar to user home.
    public void processManifestFile() {
        String version = this.getClass().getPackage().getImplementationVersion();
        LOG.info("Version: {}", version);
        Path targetFile = Paths.get(System.getProperty("user.home"), "my-project", "MANIFEST.MF");

        try {
            URL url = this.getClass().getProtectionDomain().getCodeSource().getLocation();
            JarFile jarFile = new JarFile(url.getFile());
            Manifest manifest = jarFile.getManifest();
            try(FileWriter fw = new FileWriter(targetFile.toFile())) {
                manifest.getMainAttributes().entrySet().stream().forEach( x -> {
                    try {
                        fw.write(x.getKey() + ": " + x.getValue() + "\n");
                        LOG.info("{}: {}", x.getKey(), x.getValue());
                    } catch (IOException e) {
                        LOG.error("error in write manifest, {}", e.getMessage());
                    }
                });
            }
            LOG.info("Copy MANIFEST.MF to {}", targetFile);
        } catch (Exception e) {
            LOG.error("Error in processing MANIFEST.MF file", e);
        }
    }
Folketing answered 2/7, 2020 at 6:56 Comment(0)
C
1

Keep it simple. A JAR is also a ZIP so any ZIP code can be used to read the MAINFEST.MF:

public static String readManifest(String sourceJARFile) throws IOException
{
    ZipFile zipFile = new ZipFile(sourceJARFile);
    Enumeration entries = zipFile.entries();

    while (entries.hasMoreElements())
    {
        ZipEntry zipEntry = (ZipEntry) entries.nextElement();
        if (zipEntry.getName().equals("META-INF/MANIFEST.MF"))
        {
            return toString(zipFile.getInputStream(zipEntry));
        }
    }

    throw new IllegalStateException("Manifest not found");
}

private static String toString(InputStream inputStream) throws IOException
{
    StringBuilder stringBuilder = new StringBuilder();
    try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)))
    {
        String line;
        while ((line = bufferedReader.readLine()) != null)
        {
            stringBuilder.append(line);
            stringBuilder.append(System.lineSeparator());
        }
    }

    return stringBuilder.toString().trim() + System.lineSeparator();
}

Despite the flexibility, for just reading data this answer is the best.

Contour answered 18/12, 2017 at 18:17 Comment(0)
B
1

I'm surprised that no one else proposed this solution:

File jar = ...;
try (JarFile jarFile = new JarFile(jar)) {
    Manifest manifest = jarFile.getManifest();
    // ...
}
Blase answered 1/11, 2022 at 13:55 Comment(0)
O
0

If all you need is version, use this:

//java
public class Version {
    public static final String ver=Version.class.getPackage().getImplementationVersion();
}//use as Version.ver
//kotlin
object Version{
    val ver=this::class.java.`package`.implementationVersion
}//use as Version.ver

There are also implementationVendor and implementationTitle. Similarly for specification*.


To read the manifest itself, obtain it as an InputStream via ClassLoader, then initialize a Manifest object,e.g.:

//java
public class DemoJava{
    public static void main(String[] args) {
        try (var ins = ClassLoader.getSystemResourceAsStream(JarFile.MANIFEST_NAME)) {
            if (ins != null) {
                for(var entry:new Manifest(ins).getMainAttributes().entrySet()){
                    System.out.println(entry);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//kotlin
fun main() {
    ClassLoader.getSystemResourceAsStream(JarFile.MANIFEST_NAME)?.use {
        Manifest(it).mainAttributes.forEach { println(it) }
    }
}

Reading the manifest also allows reading custom attributes e.g. release branch commit ids (if they were added).


To read the jar itself, obtain its location using CodeSource, then initializa a JarFile object, e.g.:

//java
public class DemoJava{
    public static void main(String[] args) {
        var location = DemoJava.class.getProtectionDomain().getCodeSource().getLocation();
        if (location != null) {
            try (var jarFile = new JarFile(location.toURI().getPath())) {
                for(var entry:jarFile.getManifest().getMainAttributes().entrySet()){
                    System.out.println(entry);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
//kotlin
object DummyClass
fun main(){
    DummyClass::class.java.protectionDomain.codeSource.location?.toURI()?.path.let {
        JarFile(it).manifest.mainAttributes.forEach { println(it) }
    }
}

Note: In an IDE environment, the code may run from the IDE's build directory instead of from a packaged jar/built artifact, in which case some of the above methods (or other answers here) may yield wrong results. Use your IDE's artifacts/publication's to locate the jar.

Otilia answered 5/7 at 6:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.