How to read my META-INF/MANIFEST.MF file in a Spring Boot app?
Asked Answered
W

8

23

I'm trying to read my META-INF/MANIFEST.MF file from my Spring Boot web app (contained in a jar file).

I'm trying the following code:

InputStream is = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");

Properties prop = new Properties();
prop.load( is );

But apparently there is something behind the scenes in Spring Boot that a different manifest.mf is loaded (and not my own located in META-INF folder).

Does anyone know how I can read my manifest app in my Spring Boot app?

UPDATE: After some research I noticed that using the usual way to read a manifest.mf file, in a Spring Boot application this is the Jar that is being accessed

org.springframework.boot.loader.jar.JarFile
Warring answered 30/8, 2015 at 6:39 Comment(1)
I'm also facing same issue. I have raised the similar question here but haven't got any working answers and spent lot of time on this still not able to achieve solution. Have you got the answer ?Rockie
S
23

I use java.lang.Package to read the Implementation-Version attribute in spring boot from the manifest.

String version = Application.class.getPackage().getImplementationVersion();

The Implementation-Version attribute should be configured in build.gradle

jar {
    baseName = "my-app"
    version =  "0.0.1"
    manifest {
        attributes("Implementation-Version": version)
    }
}
Sullyprudhomme answered 24/6, 2016 at 4:42 Comment(3)
Just a notice: as spring-boot issue 6619 explained, this way only worked in running a packaged jar/war. gradle bootRun will not work.Indic
Works perfectly on Jetty embedded server. to create the Jetty uber jar with maven shade plugin, add manifest entries for Implementation-Version. <Implementation-Version>${project.version}_${dev.build.timestamp}_branch:${scmBranch}_rev:${buildNumber}</Implementation-Version> Then from your code you can easily get the value from String version = Application.class.getPackage().getImplementationVersion();Approver
This suggests that the only Manifest item readily available from within a Spring-Boot app is the Implementation-Version, so just cram any info you need into there. Did I understand that correctly?Insouciance
A
11

It's simple just add this

    InputStream is = this.getClass().getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF");

    Properties prop = new Properties();
    try {
        prop.load( is );
    } catch (IOException ex) {
        Logger.getLogger(IndexController.class.getName()).log(Level.SEVERE, null, ex);
    }

Working for me.

Note:

getClass().getClassLoader() is important

and

"META-INF/MANIFEST.MF" not "/META-INF/MANIFEST.MF"

Thanks Aleksandar

Amok answered 31/8, 2015 at 21:53 Comment(4)
It works in other java applications but not in a Spring Boot application. In a Spring Boot app, the MANIFEST.MF comes from the first jar found in the list of all jars used in the application (including the app itself), and using your example, the manifest that was read comes from a standard Java lib.Warring
Hi, this definitely working for me in both case. It should work at you. Maybe you forgot maven-war-plugin or maven-jar-plugin. If you wan I can share the code with you.Amok
Hi. Yes, it works in a regular Java app, but Spring Boot introduces another ClassLoader that prevents the code above from working as expected. Actually, when calling getResourceXX, spring boot will search for the first resource that matches the name in all jars of the class path.Warring
It worked for me on a PC but on Mac it reads wrong manifest file.Nomenclator
C
7

Pretty much all jar files come with a manifest file, so your code is returning the first file it can find in the classpath.

Why would you want the manifest anyway? It's a file used by Java. Put any custom values you need somewhere else, like in a .properties file next to you .class file.

Update 2

As mentioned in a comment below, not in the question, the real goal is the version information from the manifest. Java already supplies that information with the java.lang.Package class.

Don't try to read the manifest yourself, even if you could find it.

Update 1

Note that the manifest file is not a Properties file. It's structure is much more complex than that.

See the example in the java documentation of the JAR File Specification.

Manifest-Version: 1.0
Created-By: 1.7.0 (Sun Microsystems Inc.)

Name: common/class1.class
SHA-256-Digest: (base64 representation of SHA-256 digest)

Name: common/class2.class
SHA1-Digest: (base64 representation of SHA1 digest)
SHA-256-Digest: (base64 representation of SHA-256 digest)

As you can see, Name and SHA-256-Digest occurs more than once. The Properties class cannot handle that, since it's just a Map and the keys have to be unique.

Charyl answered 30/8, 2015 at 6:45 Comment(5)
It works properly with other Java apps, but apparently Spring Boot does has a different approach. I need to read the manifest file because it's right there that Maven include information about version number, build number, Implementation vendor, etc. and I want to display that to the user.Warring
@RicardoMemoria Have a look at java.lang.Package for that information. As I said, there are many manifest files in your classpath. You'd only get your file if you are guaranteed that your jar is first. That is very rarely a guarantee you can get.Charyl
Thanks... It seems that using the java.lang.Package will solve my problem. But you say not to try to read the manifest myself. Probably the Properties class is not the best way (although it worked for what I needed), but there is a Manifest class in Java that can be used specifically to handle the content of the manifest file. Is there any specific reason why we should not try to read the manifest ourselves?Warring
@RicardoMemoria You're right, the Manifest class can be used to read a manifest file, but as you've already seen, finding the manifest file is an issue, because all Jar files have one. It's extremely rare that you'd need to read one, especially since the Package class provides the only information you'll likely need from it, in a manner that takes care of the multiple files issue.Charyl
@Charyl asks why the manifest? Because Java and Maven can EASILY and automatically put information there like the Jenkins build number, which is critical information when testers report the version where a bug is found. Have to try the hack of stuffing everything into the Implementation-Version item.Insouciance
W
4

After testing and searching on Spring docs, I found a way to read the manifest file.

First off, Spring Boot implements its own ClassLoader that changes the way how resources are loaded. When you call getResource(), Spring Boot will load the first resource that matches the given resource name in the list of all JARs available in the class path, and your app jar is not the first choice.

So, when issuing a command like:

getClassLoader().getResourceAsStream("/META-INF/MANIFEST.MF");

The first MANIFEST.MF file found in any Jar of the Class Path is returned. In my case, it came from a JDK jar library.

SOLUTION:

I managed to get a list of all Jars loaded by the app that contains the resource "/META-INF/MANIFEST.MF" and checked if the resource came from my application jar. If so, read its MANIFEST.MF file and return to the app, like that:

private Manifest getManifest() {
    // get the full name of the application manifest file
    String appManifestFileName = this.getClass().getProtectionDomain().getCodeSource().getLocation().toString() + JarFile.MANIFEST_NAME;

    Enumeration resEnum;
    try {
        // get a list of all manifest files found in the jars loaded by the app
        resEnum = Thread.currentThread().getContextClassLoader().getResources(JarFile.MANIFEST_NAME);
        while (resEnum.hasMoreElements()) {
            try {
                URL url = (URL)resEnum.nextElement();
                // is the app manifest file?
                if (url.toString().equals(appManifestFileName)) {
                    // open the manifest
                    InputStream is = url.openStream();
                    if (is != null) {
                        // read the manifest and return it to the application
                        Manifest manifest = new Manifest(is);
                        return manifest;
                    }
                }
            }
            catch (Exception e) {
                // Silently ignore wrong manifests on classpath?
            }
        }
    } catch (IOException e1) {
        // Silently ignore wrong manifests on classpath?
    }
    return null;
}

This method will return all data from a manifest.mf file inside a Manifest object.

I borrowed part of the solution from reading MANIFEST.MF file from jar file using JAVA

Warring answered 9/9, 2015 at 18:42 Comment(3)
This feels like enough code to suggest the real answer is "don't do that".Letishaletitia
Why don't you directly open the url instead of looping through all elements and checking if it matches with the appManifestFileName?Erdman
Because of a SpringBoot feature. Your application is internally executed by a launcher, so if you try to directly read the URL resource, it will point to the launcher, instead of your jar. By the end of the day, your jar is just another jar used by the launcher.Warring
A
3

I have it exploiting Spring's resource resolution:

@Service
public class ManifestService {

    protected String ciBuild;

    public String getCiBuild() { return ciBuild; }

    @Value("${manifest.basename:/META-INF/MANIFEST.MF}")
    protected void setManifestBasename(Resource resource) {
        if (!resource.exists()) return;
        try (final InputStream stream = resource.getInputStream()) {
            final Manifest manifest = new Manifest(stream);
            ciBuild = manifest.getMainAttributes().getValue("CI-Build");
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

Here we're getting CI-Build, and you can easily extend the example to load other attributes.

Adiaphorous answered 30/10, 2016 at 23:5 Comment(1)
resource.exists() always returns false in my tests, I'm using spring boot version 1.5.3.RELEASE. Exception is java.io.FileNotFoundException: ServletContext resource [/META-INF/MANIFEST.MF] cannot be resolved to URL. But the repackaged jar has my manifest at path /META-INF/MANIFEST.MF file, so IDK.Insouciance
D
2
    try {
        final JarFile jarFile = (JarFile) this.getClass().getProtectionDomain().getCodeSource().getLocation().getContent();
        final Manifest manifest = jarFile.getManifest();
        final Map<Object, Object> manifestProps = manifest.getMainAttributes().entrySet().stream()
                .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue()));
    ...
    } catch (final IOException e) {
        LOG.error("Unable to read MANIFEST.MF", e);
        ...
    }

This will work only if you launch your app via java -jar command, it won't work if one create an integration test.

Dunbarton answered 5/4, 2018 at 19:23 Comment(0)
C
1
public Properties readManifest() throws IOException {
    Object inputStream = this.getClass().getProtectionDomain().getCodeSource().getLocation().getContent();
    JarInputStream jarInputStream = new JarInputStream((InputStream) inputStream);
    Manifest manifest = jarInputStream.getManifest();
    Attributes attributes = manifest.getMainAttributes();
    Properties properties = new Properties();
    properties.putAll(attributes);
    return properties;
}
Cymoid answered 30/8, 2015 at 7:32 Comment(3)
I get this error java.lang.ClassCastException: org.springframework.boot.loader.jar.JarFile cannot be cast to java.io.InputStream. Apparently it is accessing a spring boot library in other to read my own jar file.Warring
Can you evaluate this expression: this.getClass().getProtectionDomain().getCodeSource().getLocation()Cymoid
Fails for me also with same exception, the getLocation() call returns URL jar:file:/Users/chrisinmtown/git/common-code/cmn-data/target/cmn-data-1.0.0-SNAPSHOT.jar!/BOOT-INF/classes!/Insouciance
K
0
  1. manifest.mf file is available in a jar.
  2. check the program's or application's classpath by using below code.
String classpath = System.getProperty("java.class.path");
System.out.println("classpath:"+classpath);
  1. if above lines of code print jar path(-classpath: D:\ABC\target\ABC.jar) then you can use below code. else you need to set jar path as a classpath in the your IDE(edit configuration -> modify option -> add your jar file class path.

InputStream is = this.getClass().getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF");
    
Properties prop = new Properties();
try {
    prop.load( is );
} catch (IOException ex) {
    Logger.getLogger(IndexController.class.getName()).log(Level.SEVERE, null, ex);
}
Kookaburra answered 2/3, 2023 at 10:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.