In this post, we will have a closer look at one of the basic building blocks in the Java ecosystem -
the Java ARchive or as it is more known, the JAR file.
We will describe what it is and how to create one.
What is a JAR file?
A jar file is a bundle of files that might be needed by the application. It primarily holds java class files, but it can also contain other files that your application might need (such as configuration files, images or fonts).
The jar file provides us with some useful functionality, such as:
- compression; jar files compress the contents, so the size of the file is smaller
- security; jar files can be digitally signed, so users can verify them and grant them permission accordingly
- package sealing; packages in jars can be sealed, which means that all the classes from one package must come from the same jar file
Jar files use the ZIP file format, so we can use a file archiver (such as WinRAR or 7-Zip) to browse and view the contents of a jar file.
Java compiler and class files
But before we start creating executable JAR files, we should also have a look at how to create java class files (files with the extension .class) from the source code files (files with the extension .java).
Java class files contain Java byte code that can be executed by the Java Virtual Machine (JVM).
In order to get Java class files from the source files, we need to compile the source files.
To do that we can use the java compiler which is packaged in the Java Development Kit (JDK) and is executed using the javac command. The command has the format:
javac [ options ] [ sourcefiles ] [ classes ] [ @argfiles ]
For this example, we will make use of the -sourcepath flag, which tells the compiler in which directory to search for source files, the -cp flag, which adds paths to check for classes and sources when compiling and -d flag, which changes the current directory.
So we will be executing something like:
javac path/to/srcFiles/package/Example.java -sourcepath path/to/srcFiles -cp path/to/libraries -d outputDirectory
You can read more about the other options of the java compiler and how to use them in the official documentation.
When the compilation completes successfully we end up with class files that are ready to be included in the JAR file.
Java Archive Tool and JAR files
To create a jar file we can use the Java Archive Tool, which is also bundled in the Java Development Kit and is executed with the jar command.
The jar command has the following format:
jar [options] [jar-file-name] [manifest-file] [input-files]
Where options can have the following arguments:
- c - signals the jar tool to create a jar file
- u - signals the jar tool to update a jar file
- x - signals the jar tool to extract the contents of the jar file
- t - signals the jar tool to print the table of contents for the jar file
- i - signals the jar tool to create a index for the jar file
- f - signals the jar tool to output to a file instead of stdout
- v - signals the jar tool to run in verbose mode
- 0 - signals the jar tool to not compress the jar file
- M - signals the jar tool that the default manifest file should not be generated
- m - signals the jar tool to create a manifest file using the specified file as input
- e - signals the jar tool to add an entry point header in the manifest file
- C - signals the jar tool to change directories when creating the jar
Create a jar file
The simplest way to create a jar file is to execute the command:
jar cf MyJar.jar *
The wildcard (*) signals the jar tool to match all files, so this will take all the files that are in the folder and put them in the jar file with the same folder structure. The wildcard option truly takes all files in the folder and puts them into the jar file, so usually, we want to filter and specify the files that we actually want in the jar file.
For example, in our accompanying project, we are using GIT for versioning and we are editing the project with IntelliJ, so we have the folders .git and .idea
which we would not want in the final jar file, since they would not serve any purpose in the jar, only take up space.
So to make things easier, build tools usually prepare a separate folder (most of the time named target), which is used to create the final JAR files. It prepares the folders by pointing the compiler to that folder and copying any other files that should be included in the JAR, such as the properties files, xml config files, etc.
After that, a jar command with a wildcard can be executed with that directory.
View jar file contents
The command to view the contents of a jar file is:
jar tf MyJar.jar
This command will print out the table of contents for the jar file and we can make it display more information with the -v flag.
Extract jar file contents
We can also extract specific files from the jar with:
jar xf MyJar.jar [files]
or extract all files if no files are specified:
jar xf MyJar.jar
Update jar file contents
Another command when working with jar files is the update command, where we can update specific files within a jar file.
jar uf MyJar.jar [files]
Run jar file
And finally, to run a jar file we have to execute the command:
java -jar MyJar.jar
But in order to run the jar file, it has to be executable. This means the jar has to contain information about the entry point of the application (e.g. the main class). This is supplied in the manifest file inside the jar.
A manifest file is a file in the jar that contains metadata about all the other files in the jar. The manifest file is what supports and where we can configure all the different functionalities of the jar file.
If we don’t specify a manifest file with the -m flag when creating the jar file, a default manifest file (MANIFEST.MF) is added to the jar file in the folder META-INF. The default manifest file contains only the information about the manifest version and the JDK that was used to create the jar file:
Manifest-Version: 1.0 Created-By: 1.8.0_131 (Oracle Corporation)
But since the manifest file enables us to do many things, we usually want to configure and modify the content of the manifest file. To do so we can use the -m flag of the jar tool and specify which file it should take as input to generate the final MANIFEST.MF file.
If we include the -m option then the format of the jar command is:
jar cfm [jar-file-name] [manifest-file] [input-files]
where the manifest file is a simple text file that contains the manifest headers in the format
where each of the manifest headers is on a separate line and the file has to end with an empty line.
The important thing with supplying a manifest text file is that the order of -m and -f flags match the order of arguments for the manifest text file and jar file name.
jar cmf ExampleManifest.txt Example.jar *
jar cfm Example.jar ExampleManifest.txt *
If we supply our own manifest file, we can set many different things, such as:
We can configure an entry point of the jar file in the manifest text file if we add a header:
With this, we specify that the jar should be executable and its entry point is the supplied class.
If this is the only thing we want to configure there is a convenience flag -e, which we can use instead of creating a manifest file:
jar cfe MyJar.jar com.devflection.jars.App *
We can also add external jar files to the jars classpath. If our jar depends on other external jars, we need to specify those jars on the classpath. We can add the external jar files to the classpath with the header:
Class-path: MySecondJar.jar lib/commons-lang3-3.8.1.jar
This will then load all the classes from MySecond.jar and commons-lang3-3.8.1.jar when our jar is started.
We can also supply information about the version and vendor for each package in the manifest file.
To do so we can add a set of version headers for each package in the manifest text file.
The headers are:
Name: com/devflection/jars Specification-Title: Devflection jar example classes Specification-Version: 1.0 Specification-Vendor: Devflection Implementation-Title: com.devflection.jars Implementation-Version: snapshot Implementation-Vendor: Devflection
To seal packages (enforce that all classes from the same package have to come from the same jar file) we can add the following headers:
Name: com.devflection.jars/ Sealed: true
We can seal individual by specifying a name or if we just add the Sealed: true line without specifying any package we can seal the whole jar file.
The important thing here is that the package name ends with a ’/’.
There are also a number of headers that define security constraints.
They apply only for signed applets, Java Web Start applications and JavaFX applications that are embedded in a web page or launched from a browser.
They are ignored for standalone and self-contained applications, so we will not go into detail about them here. You can read and find out more about them here.
Now, let’s use all of the information above to create an executable jar of our example application.
Our application will be a standalone executable JAR file that will depend on multiple external libraries - Apache commons, Logback and SLF4j.
We downloaded all three library JAR files from the Maven central repository and placed them in the lib folder of our example project.
Since our application will be executable it has an entry point; in our case, that is the class App.
Because of these two things we have to create a manifest input file with the headers (the ManifestInput.txt file in our project):
Main-Class: com.devflection.jars.App Class-path: lib/commons-lang3-3.8.1.jar lib/logback-classic-1.2.3.jar lib/logback-core-1.2.3.jar lib/slf4j-api-1.7.26.jar
IMPORTANT: the manifest input file must end with an empty line, otherwise, the jar tool does not parse it correctly.
Our application also depends on another resource - the log config file - so we also need to make sure that this file is included in our JAR as well.
We will create our JAR as we mentioned earlier:
- Create a new folder called target
- Compile our source files from src/main/java into target
- Copy the log config file from src/main/resources into target
- Call the jar tool and suply the manifest input file
After these steps, we should have an executable jar that we can then run with the java -jar command.
To start we should cd into the example project base directory.
Then we create a new folder with
After that, we compile our source files with
javac src/main/java/com/devflection/jars/App.java -cp lib/* -sourcepath src/main/java -d target
or if we fully write out the full library names
javac src/main/java/com/devflection/jars/App.java -cp lib/commons-lang3-3.8.1.jar;lib/logback-classic-1.2.3.jar;lib/logback-core-1.2.3.jar;lib/slf4j-api-1.7.26.jar -sourcepath src/main/java -d target
Then let’s copy the log config file
cp src/main/resources/logback.xml target/logback.xml
After that, we need to create the JAR with
jar cfm DevflectionExampleJar.jar ManifestInput.txt -C target .
IMPORTANT: the flag -C needs to end with . (a full stop).
This should create an executable jar in our base directory which we can execute by calling
java -jar DevflectionExampleJar.jar
And this should then output
2019-04-28 08:02:49 - Hello from the logger! This is our sample JAR project. 2019-04-28 08:02:49 - Hello world! First class sure is enjoyable... 2019-04-28 08:02:49 - Hello world! Economy class sure is crowded and unpleasant...
From these messages we can see that we successfully included the referenced libraries and from the format, we can also tell that the log config file is used because the default log messages would look like this:
07:52:19.929 [main] INFO com.devflection.jars.App - Hello from the logger! This is our sample JAR project. 07:52:19.940 [main] INFO com.devflection.jars.packages.first.FirstClass - Hello world! First class sure is enjoyable... 07:52:19.940 [main] INFO com.devflection.jars.packages.second.EconomyClass - Hello world! Economy class sure is crowded and unpleasant...
This is it for an introduction to jar files. You can check out the full example project on github.