Search code examples
javajar

How to use JarOutputStream to create a JAR file?


How does one create a JAR file programmatically using java.util.jar.JarOutputStream? The JAR file produced by my program looks correct (it extracts fine) but when I try loading a library from it Java complains that it cannot find files which are clearly stored inside it. If I extract the JAR file and use Sun's jar command-line tool to re-compress it the resulting library works fine. In short, something is wrong with my JAR file.

Please explain how to create a JAR file programmatically, complete with a manifest file.


Solution

  • It turns out that JarOutputStream has three undocumented quirks:

    1. Directory names must end with a '/' slash.
    2. Paths must use '/' slashes, not '\'
    3. Entries may not begin with a '/' slash.

    Here is the correct way to create a Jar file:

    public void run() throws IOException {
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
        JarOutputStream target = new JarOutputStream(new FileOutputStream("output.jar"), manifest);
        add(new File("inputDirectory"), target);
        target.close();
    }
    
    private void add(File source, JarOutputStream target) throws IOException {
        String name = source.getPath().replace("\\", "/");
        if (source.isDirectory()) {
            if (!name.endsWith("/")) {
                name += "/";
            }
            JarEntry entry = new JarEntry(name);
            entry.setTime(source.lastModified());
            target.putNextEntry(entry);
            target.closeEntry();
            for (File nestedFile : source.listFiles()) {
                add(nestedFile, target);
            }
        } else {
            JarEntry entry = new JarEntry(name);
            entry.setTime(source.lastModified());
            target.putNextEntry(entry);
            try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(source))) {
                byte[] buffer = new byte[1024];
                while (true) {
                    int count = in.read(buffer);
                    if (count == -1)
                        break;
                    target.write(buffer, 0, count);
                }
                target.closeEntry();
            }
        }
    }