javajarzipmanifest.mfzip4j

Replacing the MANIFEST.MF file in a JAR programmatically


I want to modify the MANIFEST.MF after creating the JAR to exclude certain Class-Path entries. For this I decided to use zip4j. Extraction seems to work fine but for putting the MANIFEST.MF file back into the JAR, I use the following code:

String metaInfFolderName = "META-INF";
Path extractedManifestFilePath = Paths.get("...");
ZipFile zipFile = new ZipFile("Test-Zip-File.zip");
ZipParameters zipParameters = new ZipParameters();
zipParameters.setDefaultFolderPath(metaInfFolderName);
zipFile.addFile(extractedManifestFilePath.toFile(), zipParameters);

However, this code does not work as expected: The parent directory always ends up being named NF instead of the full META-INF. It seems like the starting characters are cut off. What could be the reason for this or is there another meaningful possibility to replace files inside JARs (which are essentially just ZIPs)?

maven dependency:

<dependency>
    <groupId>net.lingala.zip4j</groupId>
    <artifactId>zip4j</artifactId>
    <version>2.6.1</version>
</dependency>

Furthermore I tried using the jar utility like described here but when invoking the command jar uf MyJAR.jar META-INF/MANIFEST.MF the MANIFEST.MF inside the JAR gets deleted instead of replaced. Using the zip utility via zip -ur MyJAR.jar "META-INF/MANIFEST.MF" works but corrupts the JAR file so it is no longer runnable:

Error: An unexpected error occurred while trying to open file MyJAR.jar

Solution

  • You can use something like this:

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.URI;
    import java.nio.file.FileSystem;
    import java.nio.file.FileSystems;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.*;
    import java.util.jar.Attributes;
    import java.util.jar.Attributes.Name;
    import java.util.jar.Manifest;
    
    public class ManifestManipulator {
    
        public static final void main(String... args) throws IOException {
            if (args.length == 0) {
                throw new IllegalArgumentException("at least the path to the JAR file expected!");
            }
            Path jarPath = Paths.get(args[0]);
            Set<String> attributeNames = args.length > 1 ? new LinkedHashSet<>(List.of(args).subList(1, args.length)) : Set.of();
    
            if (!attributeNames.isEmpty()) {
                ManifestManipulator manifestManipulator = new ManifestManipulator();
                manifestManipulator.removeManifestEntries(jarPath, attributeNames);
            } else {
                System.out.println("Warning: no entries specified to remove!");
            }
        }
    
        private void removeManifestEntries(Path jarPath, Set<String> attributeNames) throws IOException {
            System.out.println("Going to remove: " + attributeNames);
            try (FileSystem jarFS = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), Map.of())) {
                Path manifestPath = jarFS.getPath("META-INF", "MANIFEST.MF");
                Manifest manifest = readManifest(manifestPath);
                Attributes mainAttributes = manifest.getMainAttributes();
                System.out.println("Found main attribute names: " + mainAttributes.keySet());
    
                boolean removed = mainAttributes.entrySet().removeIf(entry -> attributeNames.contains(((Name) entry.getKey()).toString()));
                if (removed) {
                    writeManifest(manifestPath, manifest);
                } else {
                    System.out.println("Warning: nothing removed");
                }
            }
        }
    
        private Manifest readManifest(Path manifestPath) throws IOException {
            try (InputStream is = Files.newInputStream(manifestPath)) {
                return new Manifest(is);
            }
        }
    
        private void writeManifest(Path manifestPath, Manifest manifest) throws IOException {
            try (OutputStream os = Files.newOutputStream(manifestPath)) {
                manifest.write(os);
            }
        }
    
    }
    

    Please make sure you've added the jdk.zipfs module, which will provide a FileSystemProvider for ZIP/JAR files (see the technote).