How to use JarOutputStream to create a JAR file?

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();
        }
    }
}

There's another "quirk" to pay attention: All JarEntry's names should NOT begin with "/".

For example: The jar entry name for the manifest file is "META-INF/MANIFEST.MF" and not "/META-INF/MANIFEST.MF".

The same rule should be followed for all jar entries.


You can do it with this code:

public void write(File[] files, String comment) throws IOException {
    FileOutputStream fos = new FileOutputStream(PATH + FILE);
    JarOutputStream jos = new JarOutputStream(fos, manifest);
    BufferedOutputStream bos = new BufferedOutputStream(jos);
    jos.setComment(comment);
    for (File f : files) {
        print("Writing file: " + f.toString());
        BufferedReader br = new BufferedReader(new FileReader(f));
        jos.putNextEntry(new JarEntry(f.getName()));
        int c;
        while ((c = br.read()) != -1) {
            bos.write(c);
        }
        br.close();
        bos.flush();
    }
    bos.close();
//  JarOutputStream jor = new JarOutputStream(new FileOutputStream(PATH + FILE), manifest);

}

PATH variable: path to JAR file

FILE variable: name and format


Ok, so by request, here's Gili's code, modified to use relative paths rather than absolute ones. (Replace "inputDirectory" with the directory of your choice.) I just tested it, but if it doesn't work, lemme know.

   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);
      File inputDirectory = new File("inputDirectory");
      for (File nestedFile : inputDirectory.listFiles())
         add("", nestedFile, target);
      target.close();
   }

   private void add(String parents, File source, JarOutputStream target) throws IOException
   {
      BufferedInputStream in = null;
      try
      {
         String name = (parents + source.getName()).replace("\\", "/");

         if (source.isDirectory())
         {
            if (!name.isEmpty())
            {
               if (!name.endsWith("/"))
                  name += "/";
               JarEntry entry = new JarEntry(name);
               entry.setTime(source.lastModified());
               target.putNextEntry(entry);
               target.closeEntry();
            }
            for (File nestedFile : source.listFiles())
               add(name, nestedFile, target);
            return;
         }

         JarEntry entry = new JarEntry(name);
         entry.setTime(source.lastModified());
         target.putNextEntry(entry);
         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();
      }
      finally
      {
         if (in != null)
            in.close();
      }
   }

Here's some sample code for creating a JAR file using the JarOutputStream:

  • http://www.java2s.com/Code/Java/File-Input-Output/CreateJarfile.htm