So far we’ve seen how Java source files are compiled into binary class files, and how these are located at runtime when the containing directory is placed on the classpath. This approach works for the single file application developed previously, but most applications consist of many classes, and make use of libraries which themselves define classes and interfaces of their own. Managing and publishing directories of class files would be cumbersome, but fortunately Java defines a standard for packaging related classes into a single file called a Java ARchive, or JAR file.

Lib Hello World

After showing our groundbreaking salutatory application to our chief architect, they are mainly positive, but have some suggestions:

  • Instead of hard-coding the message, it should be supplied as input to the program
  • Messages could come from anywhere, not just the command line
  • Messages could be written anywhere, not just the console
  • Messages could be of arbitrary size
  • Other teams might want to take advantage of our work, so we should publish it as a library

After some back-and-forth, we settle on defining interfaces for sources and destinations for messages:

src/libhello/MessageSource.java

package enterprisey;

import java.io.Reader;
import java.io.IOException;

public interface MessageSource {
    Iterable<Reader> messages() throws IOException;
}

src/libhello/MessageSink.java

package enterprisey;

import java.io.Reader;
import java.io.IOException;

public interface MessageSink {
    void writeMessage(Reader message) throws IOException;
}

In addition, we define a source of messages read from the command-line, and a sink which writes messages a PrintStream:

src/libhello/CommandLinkMessageSource.java

package enterprisey;

import java.io.Reader;
import java.io.StringReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class CommandLineMessageSource implements MessageSource {
    private final String[] args;

    public CommandLineMessageSource(String[] args) {
        this.args = args;
    }

    public Iterable<Reader> messages() throws IOException {
        return new Iterable<Reader>() {
            public Iterator<Reader> iterator() {
                return new Iter();
            }
        };
    }

    private class Iter implements Iterator<Reader> {
        private int index = 0;

        public boolean hasNext() {
            return this.index < args.length;
        }

        public Reader next() {
            if (this.hasNext()) {
                String next = args[this.index];
                this.index++;
                return new StringReader(next);
            } else {
                throw new NoSuchElementException();
            }
        }
    }
}

src/libhello/PrintStreamMessageSink.java

package enterprisey;

import java.io.PrintStream;
import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;

public class PrintStreamMessageSink implements MessageSink {
    private final PrintStream dest;

    public PrintStreamMessageSink(PrintStream dest) {
        this.dest = dest;
    }

    public void writeMessage(Reader message) throws IOException {
        char[] buffer = new char[1024];
        int read;

        while ((read = message.read(buffer)) != -1) {
            CharBuffer buf = CharBuffer.wrap(buffer, 0, read);
            this.dest.print(buf);
        }

        this.dest.println();
        this.dest.flush();
    }
}

We compile these classes as usual and output the corresponding class files to the libhello directory:

javac -d classes/libhello src/libhello/*.java 

This outputs the class files under the classes/libhello directory. We can then build a JAR file from this directory:

jar --create --file libhello.jar -C classes/libhello .

this creates a libhello.jar file in the current directory. We can list the contents of this file with the --list command:

jar --lib --file libhello.jar

This shows the archive contains the .class files as their expected locations on the classpath, along with a META-INF/MANIFEST.MF file. This file is called the _manifest file and is described below.

META-INF/
META-INF/MANIFEST.MF
enterprisey/
enterprisey/MessageSource.class
enterprisey/PrintStreamMessageSink.class
enterprisey/CommandLineMessageSource$1.class
enterprisey/CommandLineMessageSource.class
enterprisey/CommandLineMessageSource$Iter.class
enterprisey/MessageSink.class

Note that JAR files are also zip files, so their contents can be listed with the unzip command:

unzip -l libhello.jar

Now we have build our library, we can re-write our application to use it:

src/app/Echo.java

package test;

import enterprisey.*;
import java.io.Reader;
import java.io.IOException;

public class Echo {
    public static void main(String[] args) {
        MessageSource source = new CommandLineMessageSource(args);
        MessageSink sink = new PrintStreamMessageSink(System.out);

        try {
            for(Reader message : source.messages()) {
                sink.writeMessage(message);
            }
        } catch (IOException ex) {
            System.err.println("Error processing messages");
        }
    }
}

As before, we compile it with javac. Since the application references the classes in libhello.jar, we have to place it on the classpath to make the class definitions available.

javac -d classes/app -cp libhello.jar src/app/Echo.java

This writes the test.Echo class to the classes/app directory. Now we can run the application. Again we have to make the classes in libhello available by adding the jar file to the classpath. We also add the build output directory for the app so the main test.Echo class can be resolved:

java -cp libhello.jar:classes/app test.Echo Hello world '!'

As expected, the application reads each message from the command line and writes it to the console

Hello
world
!

Of course we should also create a JAR for the application:

jar --create --file echo.jar -C classes/app/ .

which we can then execute by placing both the lib and app JARs on the classpath:

java -cp libhello.jar:echo.jar test.Echo Hello world '!'

Manifest files

When listing the contents of the JAR file above, we saw a META-INF/MANIFEST.MF file included. The META-INF directory within a JAR contains files used to configure aspects of the JVM. The MANIFEST.MF file contain various sections of key-value pairs used to describe the contents of the JAR. The format of the META-INF directory and MANIFEST.MF files are described in detail within the JAR file specification.

The jar tool can be used to extract the contents of the default manifest file:

jar --extract --file echo.jar META-INF/MANIFEST.MF

this is fairly minimal by default:

Manifest-Version: 1.0
Created-By: 14.0.2 (Oracle Corporation)


There are two additional manifest properties we would like to set when building the application JAR:

  • Main-Class: This is the name of the main class the JVM should load on startup
  • Class-Path: The classpath to configure

We can add these properties to a file to be added to the manifest when building the application JAR:

echo-manifest.mf

Main-Class: test.Echo
Class-Path: libhello.jar

> jar --create --file echo.jar --manifest=echo-manifest.mf -C classes/app .

Adding the main class and classpath to the application manifest means the application can be run with the -jar option without needing to specify the classpath and main class manually:

> java -jar echo.jar Hello world '!'

‘Fat’ JARs

Defining the classpath and main class within the application manifest simplifies the command required to invoke the application, but it still requires ensuring the dependency JARs are in the expected location as defined by the Class-Path entry in the application manifest. From a deployment perspective it would be easier if the application and all its dependencies were packaged in a single file.

Since a JAR file represents a single root on the classpath, multiple JAR files can be combined into one by simply extracting them all to a single directory and re-packaging them within a new JAR file. Such JARs are usually referred to as ‘fat’ (or ‘uber’) JARs.

We can first extract our library and application JARs to a temporary directory:

> unzip -d uber libhello.jar
> unzip -o -d uber echo.jar

and build a new JAR with a new manifest file. Unlike the previous application manifest, this does not need to specify the classpath since all classes are contained within the new JAR.

uber-manifest.mf

Main-Class: test.Echo

We can now build the new JAR:

> jar --create --file echo-uber.jar --manifest=uber-manifest.mf -C uber .

and run it with the -jar option as before:

> java --jar echo-uber.jar Hello world '!'

File collisions

When building the uberjar above, we simply overwrote (using the -o option for unzip) any existing files within libhello.jar when extracting the echo.jar file. This file for our simple application since there are no collisions, except for the default manifest files in the two files, which should be the same. However, this simple strategy may not work for more complicated applications. It’s possible that different JAR files will contain different files at the same path which need special handling to produce the corresponding file in the output JAR. One example is libraries which configure ServiceLoader classes which are configured by files in the META-INF/services directory.