How to write a JAR with a manifest
I had the need today to write a dummy jar with a manifest in the context of a unit test. Doing so was easy, but I ran into two goofy API problems with the jar manifest writing that tripped me up. Neither is well-documented.
A cleaned up approximation of what I was doing is:
import java.io.FileOutputStream;
import java.util.jar.*;
public class MakeJar {
public static void main(String arg[]) throws Exception {
// Name of jar file to write
String jar = arg[0];
// Read key/value pairs from arg and write as manifest attributes
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
for (int i = 1; i < arg.length; i = i + 2) {
String key = arg[i];
String value = arg[i + 1];
attributes.put(key, value); // #1
}
FileOutputStream fstream = new FileOutputStream(jar);
JarOutputStream stream = new JarOutputStream(fstream, manifest);
stream.flush();
stream.close();
}
}
You can run this with something like: java MakeJar dummy.jar a 1 b 2. This should create dummy.jar and define a manifest with "a: 1" and "b: 2" as main attributes. But if you run the code above, you'll get a ClassCastException on the line marked "#1".
The confusion here is because Attributes implements Map<Object,Object>...but also imposes additional constraints on the keys and values without declaring those in the generic types. I'm guessing this lack of specificity was done for backwards-compatibility. Attributes would ideally be declared as a Map<Attributes.Name,String>. So, if you use the normal Map.put() method in Attributes, you will get a ClassCastException if you pass anything other than an Attributes.Name object as the key or a String as the value. Of course, Attributes defines it's own specialized type-specific method putValue(String name, String value), although you'll notice even this method does not take an Attributes.Name - it takes a String key which it wraps into a Name.
This constraint is documented in the @throws javadoc for the put() method, but I think some additional words on the parameters stating the required types of the arguments would have been awfully nice.
On a side note, similar problems exist on the get side. The get() method requires an Attribute.Name, but does not document this. If you pass it a String, you will always get null. Two specialized getValue() methods are provided - one that takes a String, and one that takes a Name...which leads me to wonder why there aren't two versions of putValue... In all, this class does not win the API consistency award.
In any case, we can correct our code by changing the #1 line to be attributes.putValue(key, value);. If we do this and re-run the program you should see a dummy.jar get created with a META-INF/MANIFEST.MF file, as desired. But, if you actually look at the contents of the manifest you may be surprised as the contents of the manifest file will be empty!
It turns out that there is one required manifest attribute - Attributes.Name.MANIFEST_VERSION. If you fail to set this on the main attributes for the manifest, all attributes are silently ignored. So, a working version of my original code is below with a fixed line #1 and an added line #2.
import java.io.FileOutputStream;
import java.util.jar.*;
public class MakeJar {
public static void main(String arg[]) throws Exception {
// Name of jar file to write
String jar = arg[0];
// Read key/value pairs from arg and write as manifest attributes
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
for (int i = 1; i < arg.length; i = i + 2) {
String key = arg[i];
String value = arg[i + 1];
attributes.putValue(key, value); // #1
}
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); // #2
FileOutputStream fstream = new FileOutputStream(jar);
JarOutputStream stream = new JarOutputStream(fstream, manifest);
stream.flush();
stream.close();
}
}
This requirement is documented, but only in the method comments for Manifest.write(OutputStream) and the protected method Attributes.writeMain(). Given that you are actually setting the attributes on the Attributes class, I think this is really worth a mention in the class docs or somewhere more prominent! Or even better, you could set a default to use. AFAIK, "1.0" is the only valid version - why not make that the default? Or throw an IllegalStateException if attributes have been set but no manifest version has been set and the main attributes are being written. Surely, silently ignoring the attributes is the worst possible choice.

Hi! My name is Alex Miller and I live in St. Louis. I write code for a living and currently work for
You are right. The constraints on Attributes are documented a bit subtle. It states to map “Manifest attribute names to associated string values” which is easy to misread. The only second hint is the definition of the inner class Attribute.Name that “represents an attribute name”.
A similar, not as subtle, constraint problem is given with Properties, which implements Hashtable but only allows String-String pairs. There is a full paragraph on this in the javadocs, though.
Oh, I hate Properties with a passion. It has some similarly weird behaviors and APIs. I’ve actually blogged on it before deep in the past. Down with java.util.Properties!
This is only tangentially related, but here are a couple of articles I’ve been reading about API design and anti-patterns:
First up, an oldy but goody interview w/ Josh Bloch. He, too, dishes on Properties:
http://www.artima.com/intv/blochP.html
Secondly, Cameron “Peace” Purdy spewing bile on “idiot interfaces” including Cloneable. He makes some thought-provoking points.
http://jroller.com/cpurdy/entry/the_seven_habits_of_highly2
How about the practical approach: do it in your build.xml:
<jar basedir=”${wherever}”
jarfile=”${myjar}”>
<manifest>
<attribute name=”Some-Attribute”
value=”some value”/>
<attribute name=”Some-Other-Attribute”
value=”some other value”/>
</manifest>
</jar>
and forget about the gory details ;-)
– daniel
Well, since I’m dynamically generating these jars in a unit test, it’s far less practical to use Ant. :)
its sad they didn’t documented it all