While you will almost certainly do the majority of your Java development in an IDE such as Eclipse, VS Code, or (the author’s favorite) Intellij IDEA, all of the core tools you need to build Java applications are included in the Java Development Kit (JDK) that you have likely already downloaded in “Installing the JDK” from Oracle or another OpenJDK provider.1 In this chapter, we’ll discuss some of these command-line tools that you can use to compile, run, and package Java applications. There are many additional developer tools included in the JDK that we’ll discuss throughout this book.
For more details on IntelliJ IDEA and instructions for loading all of the examples in this book as a project, see Appendix A.
After you install Java, the core java runtime command may appear in your path (available to run) automatically. However, many of the other commands provided with the JDK may not be available unless you add the Java bin directory to your execution path. The following commands show how to do this on Linux, Mac OS X, and Windows. You will, of course, have to change the path to match the version of Java you have installed.
# Linux export JAVA_HOME=/usr/lib/jvm/java-12-openjdk-amd64 export PATH=$PATH:$JAVA_HOME/bin # Mac OS X export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-12.jdk/Contents/Home export PATH=$PATH:$JAVA_HOME/bin # Windows set JAVA_HOME=c:Program FilesJavajdk12 set PATH=%PATH%;%JAVA_HOME%in
On Mac OS X, the situation may be more confusing because recent versions ship with “stubs” for the Java commands installed. If you attempt to run one of these commands, the OS will prompt you to download Java at that time. You can preemptively grab the OpenJDK from Oracle following the instructions in “Java Tools and Environment”.
When in doubt, your go-to test for determining which version of the tools you are using is to use the -version
flag on the java and javac commands:
java -version# openjdk version "12" 2019-03-19
# OpenJDK Runtime Environment (build 12+33)
# OpenJDK 64-Bit Server VM (build 12+33, mixed mode, sharing)
javac -version# javac 12
A Java virtual machine (VM) is software that implements the Java runtime system and executes Java applications. It can be a standalone application like the java command that comes with the JDK or built into a larger application like a web browser. Usually the interpreter itself is a native application, supplied for each platform, which then bootstraps other tools written in the Java language. Tools such as Java compilers and IDEs are often implemented directly in Java to maximize their portability and extensibility. Eclipse, for example, is a pure-Java application.
The Java VM performs all the runtime activities of Java. It loads Java class files, verifies classes from untrusted sources, and executes the compiled bytecode. It manages memory and system resources. Good implementations also perform dynamic optimization, compiling Java bytecode into native machine instructions.
A standalone Java application must have at least one class containing a method called ++main()++, which is the first code to be executed upon startup. To run the application, start the VM, specifying that class as an argument. You can also specify options to the interpreter as well as arguments to be passed to the application__:__
%java
[interpreter options
]class_name
[program arguments
]
The class should be specified as a fully qualified class name, including the package name, if any. Note, however, that you don’t include the .class file extension. Here are a couple of examples:
%java animals.birds.BigBird
%java MyTest
The interpreter searches for the class in the classpath, a list of directories and archive files where classes are stored. We’ll discuss the classpath in detail in the next section. The classpath can be specified either by an environment variable or with the command-line option -classpath
. If both are present, the command-line option is used.
Alternately, the java command can be used to launch an “executable” Java archive (JAR) file:
% java -jar spaceblaster.jar
In this case, the JAR file includes metadata with the name of the startup class containing the main()
method, and the classpath becomes the JAR file itself.
After loading the first class and executing its main()
method, the application can reference other classes, start additional threads, and create its user interface or other structures, as shown in Figure 3-1.
The main()
method must have the right method signature. A method signature is the set of information that defines the method. It includes the method’s name, arguments, and return type, as well as type and visibility modifiers. The main()
method must be a public
, static
method that takes an array of String
objects as its argument and does not return any value (void
):
public
static
void
main
(
String
[]
myArgs
)
The fact that main()
is a public
and static
method simply means that it is globally accessible and that it can be called directly by name. We’ll discuss the implications of visibility modifiers such as public
and the meaning of static
in Chapters Chapter 4 and Chapter 5.
The main()
method’s single argument, the array of String
objects, holds the command-line arguments passed to the application. The name of the parameter doesn’t matter; only the type is important. In Java, the content of myArgs
is an array. (More on arrays in Chapter 4.) In Java, arrays know how many elements they contain and can happily provide that information:
int
numArgs
=
myArgs
.
length
;
myArgs[0]
is the first command-line argument, and so on.
The Java interpreter continues to run until the main()
method of the initial class file returns and until any threads that it has started also exit. (More on threads in Chapter 9.) Special threads designated as daemon threads are automatically terminated when the rest of the application has completed.
Although it is possible to read host environment variables from Java, it is discouraged for application configuration. Instead, Java allows any number of system property values to be passed to the application when the VM is started. System properties are simply name-value string pairs that are available to the application through the static System.getProperty()
method. You can use these properties as a more structured and portable alternative to command-line arguments and environment variables for providing general configuration information to your application at startup. Each system property is passed to the interpreter on the command line using the -D
option followed by name
=
value
. For example:
% java -Dstreet=sesame -Dscene=alley animals.birds.BigBird
The value of the street
property is then accessible this way:
String
street
=
System
.
getProperty
(
"street"
);
An application can get its configuration in myriad other ways, including via files or network configuration at runtime.
The concept of a path should be familiar to anyone who has worked on a DOS or Unix platform. It’s an environment variable that provides an application with a list of places to look for some resource. The most common example is a path for executable programs. In a Unix shell, the PATH
environment variable is a colon-separated list of directories that are searched, in order, when the user types the name of a command. The Java CLASSPATH
environment variable, similarly, is a list of locations that are searched for Java class files. Both the Java interpreter and the Java compiler use the CLASSPATH
when searching for packages and Java classes.
An element of the classpath can be a directory or a JAR file. Java also supports archives in the conventional ZIP format, but JAR and ZIP are really the same format. JARs are simple archives that include extra files (metadata) that describe each archive’s contents. JAR files are created with the JDK’s jar utility; many tools for creating ZIP archives are publicly available and can be used to inspect or create JAR files as well. The archive format enables large groups of classes and their resources to be distributed in a single file; the Java runtime automatically extracts individual class files from the archive as needed.
The precise means and format for setting the classpath vary from system to system. On a Unix system (including Mac OS X), you set the CLASSPATH
environment variable with a colon-separated list of directories and class archive files:
%export
CLASSPATH=/home/vicky/Java/classes:/home/josh/lib/foo.jar:.
This example specifies a classpath with three locations: a directory in the user’s home, a JAR file in another user’s directory, and the current directory, which is always specified with a dot (.
). The last component of the classpath, the current directory, is useful when you are tinkering with classes.
On a Windows system, the CLASSPATH
environment variable is set with a semicolon-separated list of directories and class archive files:
C:> set CLASSPATH=C:homevickyJavaclasses;C:homejoshlibfoo.jar;.
The Java launcher and the other command-line tools know how to find the core classes, which are the classes included in every Java installation. The classes in the java.lang
, java.io
, java.net
, and javax.swing
packages, for example, are all core classes so you do not need to include these classes in your classpath.
The classpath may also include “*” wildcards that match all JAR files within a directory. For example:
export
CLASSPATH
=/
home
/
pat
/
libs
/*
To find other classes, the Java interpreter searches the elements of the classpath in order. The search combines the path location and the components of the fully qualified class name. For example, consider a search for the class animals.birds.BigBird
. Searching the classpath directory /usr/lib/java means the interpreter looks for an individual class file at /usr/lib/java/animals/birds/BigBird.class. Searching a ZIP or JAR archive on the classpath, say /home/vicky/myutils.jar, means that the interpreter looks for component file animals/birds/BigBird.class within that archive.
For the Java runtime, java, and the Java compiler, javac, the classpath can also be specified with the -classpath
option:
% javac -classpath /home/pat/classes:/utils/utils.jar:. Foo.java
If you don’t specify the CLASSPATH
environment variable or command-line option, the classpath defaults to the current directory (.
); this means that the files in your current directory are normally available. If you change the classpath and don’t include the current directory, these files will no longer be accessible.
We suspect that about 80 percent of the problems that newcomers have when first learning Java are classpath-related. You may wish to pay particular attention to setting and checking the classpath when getting started. If you’re working inside an IDE, it may remove some or all of the burden of managing the classpath. Ultimately, however, understanding the classpath and knowing exactly what is in it when your application runs is very important to your long-term sanity. The javap command, discussed next, can be useful in debugging classpath issues.
A useful tool to know about is the javap command. With javap, you can print a description of a compiled class. You don’t need the source code, and you don’t even need to know exactly where it is, only that it is in your classpath. For example:
% javap java.util.Stack
prints the information about the java.util.Stack
class:
Compiled
from
"Stack.java"
public
class
java
.
util
.
Stack
<
E
>
extends
java
.
util
.
Vector
<
E
>
{
public
java
.
util
.
Stack
();
public
E
push
(
E
);
public
synchronized
E
pop
();
public
synchronized
E
peek
();
public
boolean
empty
();
public
synchronized
int
search
(
java
.
lang
.
Object
);
}
This is very useful if you don’t have other documentation handy and can also be helpful in debugging classpath problems. Using javap, you can determine whether a class is in the classpath and possibly even which version you are looking at (many classpath issues involve duplicate classes in the classpath). If you are really curious, you can try javap with the -c
option, which causes it to also print the JVM instructions for each method in the class!
As of Java 9, an alternative to the classic classpath approach (which remains available), you can take advantage of the new modules approach to Java applications. Modules allow for more fine-grained, performant application deployments—even when the application in question is large. They require extra setup so we won’t be tackling them in this book, but it is important to know that any commercially distributed app will likely be module-based. You can check out Java 9 Modularity by Paul Bakker and Sander Mak for more details and help modularizing your own larger projects if you start looking to share your work beyond just posting source to public repositories.
In this section, we’ll say a few words about javac, the Java compiler in the JDK. The javac compiler is written entirely in Java, so it’s available for any platform that supports the Java runtime system. javac turns Java source code into a compiled class that contains Java bytecode. By convention, source files are named with a .java extension; the resulting class files have a .class extension. Each source code file is considered a single compilation unit. As you’ll see in Chapter 5, classes in a given compilation unit share certain features, such as package
and import
statements.
javac allows one public class per file and insists that the file have the same name as the class. If the filename and class name don’t match, javac issues a compilation error. A single file can contain multiple classes, as long as only one of the classes is public and is named for the file. Avoid packing too many classes into a single source file. Packing classes together in a .java file only superficially associates them. In Chapter 5, we’ll talk about inner classes, classes that contain other classes and interfaces.
As an example, place the following source code in the file BigBird.java:
package
animals
.
birds
;
public
class
BigBird
extends
Bird
{
...
}
Next, compile it with:
% javac BigBird.java
Unlike the Java interpreter, which takes just a class name as its argument, javac needs a filename (with the .java extension) to process. The previous command produces the class file BigBird.class in the same directory as the source file. While it’s nice to see the class file in the same directory as the source for this example, for most real applications, you need to store the class file in an appropriate place in the classpath.
You can use the -d
option with javac to specify an alternative directory for storing the class files javac generates. The specified directory is used as the root of the class hierarchy, so .class files are placed in this directory or in a subdirectory below it, depending on whether the class is contained in a package. (The compiler creates intermediate subdirectories automatically, if necessary.) For example, we can use the following command to create the BigBird.class file at /home/vicky/Java/classes/animals/birds/BigBird.class:
% javac -d /home/vicky/Java/classes BigBird.java
You can specify multiple .java files in a single javac command; the compiler creates a class file for each source file. But you don’t need to list the other classes your class references as long as they are in the classpath in either source or compiled form. During compilation, Java resolves all other class references using the classpath.
The Java compiler is more intelligent than your average compiler, replacing some of the functionality of a make utility. For example, javac compares the modification times of the source and class files for all classes and recompiles them as necessary. A compiled Java class remembers the source file from which it was compiled, and as long as the source file is available, javac can recompile it if necessary. If, in the previous example, class BigBird
references another class, animals.furry.Grover
, javac looks for the source file Grover.java in an animals.furry
package and recompiles it if necessary to bring the Grover.class class file up-to-date.
By default, however, javac checks only source files that are referenced directly from other source files. This means that if you have an out-of-date class file that is referenced only by an up-to-date class file, it may not be noticed and recompiled. For that and many other reasons, most projects use a real build utility such as Gradle to manage builds, packaging, and more.
Finally, it’s important to note that javac can compile an application even if only the compiled (binary) versions of some of the classes are available. You don’t need source code for all your objects. Java class files contain all the data type and method signature information that source files contain, so compiling against binary class files is as typesafe (and exception safe) as compiling with Java source code.
Java 9 introduced a utility call jshell which allows you to try out bits of Java code and see the results immediately. jshell is a REPL—a Read Evaluate Print Loop. Many languages have them and prior to Java 9 there were many third-party variations available, but nothing built into the JDK itself. We saw a hint of what jshell can do in the previous chapter; let’s look a little more carefully at its capabilities.
You can use a terminal or command window from your operating system, or you can open a terminal tab in IntelliJ IDEA as shown in Figure 3-2. Just type jshell
at your command prompt and you’ll see a bit of version information along with a quick reminder about how to view help from within the REPL.
Let’s go ahead and try that help command now:
| Welcome to JShell -- Version 12 | For an introduction type: /help intro jshell> /help intro | | intro | ===== | | The jshell tool allows you to execute Java code, getting immediate results. | You can enter a Java definition (variable, method, class, etc), like: int x = 8 | or a Java expression, like: x + x | or a Java statement or import. | These little chunks of Java code are called 'snippets'. | | There are also the jshell tool commands that allow you to understand and | control what you are doing, like: /list | | For a list of commands: /help
jshell is quite powerful and we won’t be using all of its features in this book. However, we will certainly be using its ability to try Java code and make quick tweaks and try again throughout most of the remaining chapters. Think back to our HelloJava2
example “HelloJava2: The Sequel”. We can create user interface elements like that JFrame
right in the REPL and then manipulate them—all while getting immediate feedback! No need to save, compile, run, edit, save, compile, run, etc. Let’s try:
jshell> JFrame frame = new JFrame( "HelloJava2" ) | Error: | cannot find symbol | symbol: class JFrame | JFrame frame = new JFrame( "HelloJava2" ); | ^----^ | Error: | cannot find symbol | symbol: class JFrame | JFrame frame = new JFrame( "HelloJava2" ); | ^----^
Oops! jshell is smart and feature rich, but it is also quite literal. Remember that if you want to use a class not included in the default package, you have to import it. That’s true in Java source files, and it’s true when using jshell. Let’ try again:
jshell> import javax.swing.* jshell> JFrame frame = new JFrame( "HelloJava2" ) frame ==> javax.swing.JFrame[frame0,0,23,0x0,invalid,hidden ... led=true]
That’s better. A little strange, probably, but better. Our frame
object has been created. That extra information after the ==>
arrow is just the details about our JFrame
such as its size (0x0
) and position on screen (0,23
). Other types of objects will show other details. Let’s give our frame some width and height like we did before and get our frame on the screen where we can see it.
jshell> frame.setSize(300,200) jshell> frame.setLocation(400,400) jshell> frame.setVisible(true)
You should see a window pop up right before your very eyes! It will be resplendent in modern finery as shown in Figure 3-3.
By the way, don’t worry about making mistakes in the REPL. You’ll see an error message but you can just correct whatever was wrong and keep going. As a quick example, imagine making a typo when trying to change the size of the frame:
jshell> frame.setsize(300,300) | Error: | cannot find symbol | symbol: method setsize(int,int) | frame.setsize(300,300) | ^-----------^
Java is case-sensitive so setSize()
is not the same as setsize()
. jshell gives you the same kind of error information that the Java compiler would, but presents it inline. Correct that mistake and watch the frame get a little bigger!
Amazing! Well, alright, perhaps it is less than useful, but we’re just starting. Let’s add some text using the JLabel
class:
jshell> JLabel label = new JLabel("Hi jshell!") label ==> javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0. ... rticalTextPosition=CENTER] jshell> frame.add(label) $8 ==> javax.swing.JLabel[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=Hi jshell!,verticalAlignment=CENTER,verticalTextPosition=CENTER]
Neat, but why didn’t our label show up in the frame? We’ll go into much more detail on this in the chapter on user interfaces, but Java allows some graphical changes to build up before realizing them on your screen. This can be an immensely efficient trick but it can sometimes catch you off guard. Let’s force the frame to redraw itself:
jshell> frame.revalidate() jshell> frame.repaint()
Now we can see our label. Some actions will automatically trigger a call to revalidate()
or repaint()
. Any component already added to our frame before we make it visible, for example, would appear right away when we do show the frame. Or we can remove the label similar to how we added it. Watch again to see what happens when we change the size of the frame immediately after removing our label.
jshell> frame.remove(label) // as with add(), things don't change immediately jshell> frame.setSize(400,150)
See? We have a new, slimmer window and no label—all without forced repainting. We’ll do more work with UI elements in later chapters, but let’s try one more tweak to our label just to show you how easy it is to try out new ideas or methods you looked up in the documenatation. We can center the label’s text, for example, resulting in something like Figure 3-7.
jshell> frame.add(label) $45 ==> javax.swing.JLabel[,0,0,300x278,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=8388608,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,horizontalAlignment=LEADING,horizontalTextPosition=TRAILING,iconTextGap=4,labelFor=,text=Hi jshell!,verticalAlignment=CENTER,verticalTextPosition=CENTER] jshell> frame.revalidate() jshell> frame.repaint() jshell> label.setHorizontalAlignment(JLabel.CENTER)
We know this was another whirlwind tour with several bits of code that might not make sense yet, like why is CENTER
all caps? or why is the class name JLabel
used before our center alignment? Hopefully typing along, probably making a few small mistakes, correcting them, and seeing the results makes you want to know more. We just want to make sure you have the tools needed to continue playing along as you go through the rest of this book. Like so many other skills, programming benefits from doing in addition to reading!
Java Archive (JAR) files are Java’s suitcases. They are the standard and portable way to pack up all the parts of your Java application into a compact bundle for distribution or installation. You can put whatever you want into a JAR file: Java class files, serialized objects, data files, images, audio, etc. A JAR file can also carry one or more digital signatures that attest to its integrity and authenticity. A signature can be attached to the file as a whole or to individual items in the file.
The Java runtime system can load class files directly from an archive in your CLASSPATH
, as described earlier. Nonclass files (data, images, etc.) contained in your JAR file can also be retrieved from the classpath by your application using the getResource()
method. Using this facility, your code doesn’t have to know whether any resource is in a plain file or a member of a JAR archive. Whether a given class or data file is an item in a JAR file or an individual file on the classpath, you can always refer to it in a standard way and let Java’s class loader resolve the location.
Items stored in JAR files are compressed with the standard ZIP file compression. Compression makes downloading classes over a network much faster. A quick survey of the standard Java distribution shows that a typical class file shrinks by about 40 percent when it is compressed. Text files such as HTML or ASCII containing English words often compress to one-tenth their original size or less. (On the other hand, image files don’t normally get smaller when compressed as most common image formats are themselves a compression format.)
Java also has an archive format called Pack200, which is optimized specifically for Java class bytecode and can achieve over four times greater compression of Java classes than ZIP alone. We’ll talk about Pack200 later in this chapter.
The jar utility provided with the JDK is a simple tool for creating and reading JAR files. Its user interface isn’t particularly friendly. It mimics the Unix tar (tape archive) command. If you’re familiar with tar, you’ll recognize the following incantations:
jar -cvf jarFile path [ path ] [ … ]
Create jarFile
containing path
(s).
jar -tvf jarFile [ path ] [ … ]
List the contents of jarFile
, optionally showing just path
(s).
jar -xvf jarFile [ path ] [ … ]
Extract the contents of jarFile
, optionally extracting just path
(s).
In these commands, the flag letters c
, t
, and x
tell jar whether it is creating an archive, listing an archive’s contents, or extracting files from an archive. The f
means that the next argument is the name of the JAR file on which to operate. The optional v
flag tells jar to be verbose when displaying information about files. In verbose mode, you get information about file sizes, modification times, and compression ratios.
Subsequent items on the command line (i.e., anything aside from the letters telling jar what to do and the file on which jar should operate) are taken as names of archive items. If you’re creating an archive, the files and directories you list are placed in it. If you’re extracting, only the filenames you list are extracted from the archive. (If you don’t list any files, jar extracts everything in the archive.)
For example, let’s say we have just completed our new game, spaceblaster. All the files associated with the game are in three directories. The Java classes themselves are in the spaceblaster/game directory, spaceblaster/images contains the game’s images, and spaceblaster/docs contains associated game data. We can pack all this in an archive with this command:
% jar -cvf spaceblaster.jar spaceblaster
Because we requested verbose output, jar tells us what it is doing:
adding:
spaceblaster
/
(
in
=
0
)
(
out
=
0
)
(
stored
0
%)
adding:
spaceblaster
/
game
/
(
in
=
0
)
(
out
=
0
)
(
stored
0
%)
adding:
spaceblaster
/
game
/
Game
.
class
(
in
=
8035
)
(
out
=
3936
)
(
deflated
51
%)
adding:
spaceblaster
/
game
/
Planetoid
.
class
(
in
=
6254
)
(
out
=
3288
)
(
deflated
47
%)
adding:
spaceblaster
/
game
/
SpaceShip
.
class
(
in
=
2295
)
(
out
=
1280
)
(
deflated
44
%)
adding:
spaceblaster
/
images
/
(
in
=
0
)
(
out
=
0
)
(
stored
0
%)
adding:
spaceblaster
/
images
/
spaceship
.
gif
(
in
=
6174
)
(
out
=
5936
)
(
deflated
3
%)
adding:
spaceblaster
/
images
/
planetoid
.
gif
(
in
=
23444
)
(
out
=
23454
)
(
deflated
0
%)
adding:
spaceblaster
/
docs
/
(
in
=
0
)
(
out
=
0
)
(
stored
0
%)
adding:
spaceblaster
/
docs
/
help1
.
html
(
in
=
3592
)
(
out
=
1545
)
(
deflated
56
%)
adding:
spaceblaster
/
docs
/
help2
.
html
(
in
=
3148
)
(
out
=
1535
)
(
deflated
51
%)
jar creates the file spaceblaster.jar and adds the directory spaceblaster, adding the directories and files within spaceblaster to the archive. In verbose mode, jar reports the savings gained by compressing the files in the archive.
We can unpack the archive with this command:
% jar -xvf spaceblaster.jar
Likewise, we can extract an individual file or directory with:
%jar -xvf spaceblaster.jar
filename
But, of course, you normally don’t have to unpack a JAR file to use its contents; Java tools know how to extract files from archives automatically. We can list the contents of our JAR with the command:
% jar -tvf spaceblaster.jar
Here’s the output; it lists all the files, their sizes, and their creation times:
0
Thu
May
15
12
:
18
:
54
PDT
2003
META
-
INF
/
1074
Thu
May
15
12
:
18
:
54
PDT
2003
META
-
INF
/
MANIFEST
.
MF
0
Thu
May
15
12
:
09
:
24
PDT
2003
spaceblaster
/
0
Thu
May
15
11
:
59
:
32
PDT
2003
spaceblaster
/
game
/
8035
Thu
May
15
12
:
14
:
08
PDT
2003
spaceblaster
/
game
/
Game
.
class
6254
Thu
May
15
12
:
15
:
18
PDT
2003
spaceblaster
/
game
/
Planetoid
.
class
2295
Thu
May
15
12
:
15
:
26
PDT
2003
spaceblaster
/
game
/
SpaceShip
.
class
0
Thu
May
15
12
:
17
:
00
PDT
2003
spaceblaster
/
images
/
6174
Thu
May
15
12
:
16
:
54
PDT
2003
spaceblaster
/
images
/
spaceship
.
gif
23444
Thu
May
15
12
:
16
:
58
PDT
2003
spaceblaster
/
images
/
planetoid
.
gif
0
Thu
May
15
12
:
10
:
02
PDT
2003
spaceblaster
/
docs
/
3592
Thu
May
15
12
:
10
:
16
PDT
2003
spaceblaster
/
docs
/
help1
.
html
3148
Thu
May
15
12
:
10
:
02
PDT
2003
spaceblaster
/
docs
/
help2
.
html
Note that the jar command automatically adds a directory called META-INF to our archive. The META-INF directory holds files describing the contents of the JAR file. It always contains at least one file: MANIFEST.MF. The MANIFEST.MF file can contain a “packing list” naming the files in the archive along with a user-definable set of attributes for each entry.
The manifest is a text file containing a set of lines in the form keyword: value. The manifest is, by default, empty and contains only JAR file version information:
Manifest
-
Version:
1.0
Created
-
By:
1.7
.
0_07
(
Oracle
Corporation
)
It is also possible to sign JAR files with a digital signature. When you do this, digest (checksum) information is added to the manifest for each archived item (as shown next) and the META-INF directory holds digital signature files for items in the archive.
Name:
com
/
oreilly
/
Test
.
class
SHA1
-
Digest:
dF2GZt8G11dXY2p4olzzIc5RjP3
=
...
You can add your own information to the manifest descriptions by specifying your own supplemental, manifest file when you create the archive. This is one possible place to store other simple kinds of attribute information about the files in the archive, perhaps version or authorship information.
For example, we can create a file with the following keyword: value lines:
Name:
spaceblaster
/
images
/
planetoid
.
gif
RevisionNumber:
42.7
Artist
-
Temperament:
moody
To add this information to the manifest in our archive, place it in a file called myManifest.mf and give the following jar command:
% jar -cvmf myManifest.mf spaceblaster.jar spaceblaster
We included an additional option, m
, which specifies that jar should read additional manifest information from the file given on the command line. How does jar know which file is which? Because m
is before f
, it expects to find the manifest information before the name of the JAR file it will create. If you think that’s awkward, you’re right; get the names in the wrong order, and jar does the wrong thing.
An application can get this manifest information from a JAR file using the java.util.jar.Manifest
class.
Aside from attributes, you can put a few special values in the manifest file. One of these, Main-Class
, allows you to specify the class containing the primary main()
method for an application contained in the JAR:
Main
-
Class:
com
.
oreilly
.
Game
If you add this to your JAR file manifest (using the m
option described earlier), you can run the application directly from the JAR:
% java -jar spaceblaster.jar
Some GUI environments used to support double-clicking on the JAR file to launch the application. The interpreter looks for the Main-Class
value in the manifest, then loads the designated class as the application’s startup class. This feature seems to be in flux and is not supported on all operatings systems so you may need to investigate other Java application distribution options if you are creating an app you want to share with others.
Pack200 is an archive format that is optimized for storing compiled Java class files. Pack200 is not a new form of compression, but rather an efficient layout for class information that eliminates many types of waste and redundancy across related classes. It is effectively a bulk class-file format that deconstructs many classes and reassembles their parts efficiently into one catalog. This then allows a standard compression format like ZIP to work at maximum efficiency on the archive, achieving four or more times greater compression. The Java runtime does not understand the Pack200 format, so you cannot place archives of this type into the classpath. Instead, it is mainly an intermediate format that is very useful for transferring application JARs over the network for applets or other kinds of web-based applications.
It was popular for delivering applets around the web back in the day, but as applets have faded (well, disappeared), so too has the utility of the Pack200 format. You may still encounter some .pack.gz files so we wanted to mention the tools you would use, but the tools themselves have been removed in Java 14.
You can convert a JAR to and from Pack200 format with the pack200 and unpack200 commands supplied with the JDK and OpenJDK prior to Java version 14.
For example, to convert foo.jar to foo.pack.gz, use the pack200 command:
% pack200 foo.pack.gz foo.jar
To convert foo.pack.gz to foo.jar:
% unpack200 foo.pack.gz foo.jar
Note that the Pack200 process completely tears down and reconstructs your classes at the class level, so the resulting foo.jar file will not be byte-for-byte the same as the original.
Alrighty then. There are obviously quite a few tools in the Java ecosystem—they got the name right with the initial bundling of everything into the Java Development “Kit”. You won’t use every tool mentioned above right away so don’t worry if the list of utilities seems a little overwhelming. We will focus on using the javac
compiler utility as you wade farther and farther into the Java waters. Even then, the compiler and several other tools are helpfully wrapped up behind buttons in your IDE. Our goal for this chapter is to make sure you know what tools are out there so that you can come back for details when you need them.
Hopefully now that you’ve seen some of the arsenal available to help process and package Java code, you’re ready to write some of that code. The next several chapters lay the foundations for doing just that, so let’s dive in!
1 You can search for “OpenJDK provider” to find a current list of options as well as some useful comparisons between providers.