If you learned to write Java in IntelliJ, NetBeans, or Eclipse, and suddenly you’re faced with using the command line Java tools and messing with jars, this is for you.
There are really only three commands you need to know about:
javac, which compiles Java source code into a format that you can run
java, which runs those compiled files
jar, which packages up compiled files along with configuration, images, or other files into a package
Let’s start with compiling and running hello world.
Save this in
HelloWorld.java. Remember, in Java each file defines one class and the file must be named the same as the class. We compile it by opening a shell and in the same directory as the file running
The output of this is another file,
HelloWorld.class, in the same directory. Now, if you are familiar with the shell, you might expect to run this with
.class file is not program in a form that any major operating system today understands. It was never meant to be. Instead it is a format called bytecode that is designed to be run by another program, called the virtual machine. The same bytecode can run anywhere the virtual machine can run. We run the virtual machine with the
java command. Now, you might expect to run it with
In fact, it prints
Error: Could not find or load main class HelloWorld.class Caused by: java.lang.ClassNotFoundException: HelloWorld.class
At this point you should be thinking, “Wait a minute, what do you mean you can’t find it? It’s right there!” We need to pause and get into the world the Java virtual machine.
To work on a wide range of machines, the Java virtual machine is a self contained world. When we run the
java command, we pass it the name of class, not the name of a file. In
HelloWorld.java, we defined the
HelloWorld class. The virtual machine starts up, loads all classes in the bytecode you point it to, and looks for the class in there.
Note: Any class you pass must be one that the Java virtual machine can use as a starting point for the program. That is, it must define a
public static void main method. Otherwise the Java virtual machine will complain that there is no
So how do you point it to bytecode? You pass directories containing
.class files to the
-cp (read: classpath) argument of the
java command. Since we’re in the same directory as the
.class file, we can just tell it
., a synonynm for the current directory.
java -cp . HelloWorld
The Java virtual machine starts, loads
.class files from the current directory, and then looks for one named
HelloWorld to run. It finds it, finds that it has a
public static void main method, and runs that.
Note: This may seem very indirect, but when we start to add more—and different—things to our classpath, this indirection will become necessary.
Now, say that you have a library of class files in
/lib/mylib, another in
/lib/otherlib, and your own code in
/home/me/myproject. How do you tell the virtual machine to load the classes in all of these locations? You list them all as arguments to
-cp, separated by
:. So you would run
What happens if there’s a class named
HelloWorld in one of those libraries, though? The Java virtual machine will pick one of them. Whether it’s the right one is another matter, and classes expecting one may find themselves calling the other. To get around this, Java introduced the notion of packages.
We can change our
HelloWorld.java file to put the class in a package.
We compile it again and try to run it (
javac HelloWorld.java && java -cp . HelloWorld), and…it fails!
Error: Could not find or load main class HelloWorld Caused by: java.lang.NoClassDefFoundError: com/madhadron/HelloWorld (wrong name: HelloWorld)
HelloWorld.class is right there! What’s wrong?
When we tell the Java virtual machine what class to start with, we have to provide its “fully qualified” name, that is, the package followed by the name of the class. In our case that is
com.madhadron.HelloWorld. So, let’s try that. We run
Error: Could not find or load main class com.madhadron.HelloWorld Caused by: java.lang.ClassNotFoundException: com.madhadron.HelloWorld
Just like Java enforces that a class named
HelloWorld must be in a file named
HelloWorld.java, a class in a package
com.madhadron must be in a subdirectory
com/madhadron/. Since we are passing the current directory as our classpath, we need to put
So we run
javac HelloWorld.java mkdir -p com/madhadron mv HelloWorld.class com/madhadron/
Now our folder has the contents
HelloWorld.java com/ madhadron/ HelloWorld.class
And when we run
java -cp . com.madhadron.HelloWorld, it successfully loads the class, finds the one we want to run, and runs it.
Note: Package names can prevent class names from colliding, but how do you prevent package names from colliding? The Java community decided to make that someone else’s problem. When you publish a library, you’re supposed to have a domain name, like
madhadron.com. You reverse all the components of the domain, add another field to identify your package, and use that as your package name. For example, if I publish this hello world program as a library called
helloworld, it would be in the package
Since another system entirely already garuantees that one organization or person owns a domain name, this way we don’t get conflicts. No one enforces this. You could put out a library that puts its code in a package beginning with
com.google, but anyone using it would complain mightily and report it as a bug.
Now, say you want to distribute your library. You would probably make a zip file of all your class files in the right subdirectories for someone to uncompress on their machine. Since everyone is already doing this, Java introduced a convenience called jar files. A jar file is a zip file with an extra bit of data saying what code they are carrying. You can add a jar file on your classpath like you would a directory containing class files.
So say I create a jar file of my
helloworld library, called
helloworld.jar, containing the class
com.madhadron.helloworld.HelloWorld. You put it in
/lib/helloworld.jar, and run it with
How do you create these jar files? With a tool called
jar. At first glance the options to the
jar command may seem strange. That is because they are inherited from a much older Unix tool called
tar. This may not have been the best choice, but Java was created at Sun Microsystems, a company that specialized in Unix workstations. Their engineers used
tar all the time, and so copied what they were used to.
Now, for our
helloworld library, after compiling
HelloWorld.java, our directory has
HelloWorld.java com/ madhadron/ helloworld/ HelloWorld.class
helloworld.jar from this by running
jar cf helloworld.jar com
If we had a second directory we wanted as well, we could add its name after
com, separated by a space. The same for a third, or a fourth. Why would we do that? Imagine that we have a jar file
otherlibrary.jar containing a library our program depends on, and we want to make one jar file containing both the library and our program. You might think you could say
# THIS DOES NOT WORK jar cf helloworld.jar com otherlibrary.jar
This doesn’t work. When we run the Java virtual machine, it won’t look inside jars for other jars to load classes from. Instead, we need to unzip the library jar, and then build a new jar with both folders.
We can replace
xf to unzip jars. So we can run
jar xf otherlibrary.jar
and we will get its contents. Say it’s put out by
example.org. Then we now have a folder containing
com/ madhadron/ helloworld/ HelloWorld.class HelloWorld.java org/ example/ otherlibrary/ SomeClass.class otherlibrary.jar
And we can package up our jar with
jar cf helloworld.jar com org
We can give that jar to someone and they can run our program in it by running
java -cp helloworld.jar com.madhadron.helloworld.HelloWorld
That seems like a lot, though. Do they really need to know the name of our class to run this zip file we have given them? No, they don’t. The Java virtual machine recognizes a special case of a jarfile. We can run a slightly different
jar command to tell it what the class to run is:
jar cfe helloworld.jar com.madhadron.helloworld.HelloWorld com org
Now we can run
java -jar helloworld.jar
and it will directly run our program.