Compiling Java code with a command-line

I have only a beginner level knowledge in Java and haven’t done any serious coding with Java up until recently. I have been using C/C++ extensively in my university projects and other open-source projects (especially in symengine) I contribute to. So far, my experience with Java has been something completely different from what I am normally used to.

Java is very much IDE oriented. Even the most simple Java project uses an IDE. IDEs make coding and managing the project very easy. However, this is in clear contrast to how C/C++ projects are developed. For example, in my work on symengine, I only use a text editor like gedit (with some plugins) and the command-line.

So I was a bit curious to find out what is going on under an IDE when compiling Java code into bytecode and wanted to do that by myself with a command-line. Let’s play with a couple of Java files and try to compile them into bytecode using Java command-line tools.

Compiling and running a single file

Compiling a single file is very straightforward. Suppose we have a Java file A.java with the following content.

    package org.application;

    public class A {
        public static void main(String[] args) {
            System.out.println("Inside A.main()");
        }
    }

Java assumes that directory structure adheres to the package definition. Inside A.java file, we have defined it in the package org.application. So, I have created a directory structure that matches the package definition and put A.java file in the appropriate place. Below is my directory tree (generated using tree command). Here “.” denotes the current directory (Let’s call it root hereafter).

    .
    └── org
        └── application
            └── A.java

Now you can compile A.java to bytecode using javac command. There are two ways to do this. You can use the following single-liner,

    javac org/application/A.java

or change directory to org/application from the root and then invoke javac.

    cd org/application/
    javac A.java

After executing the above command(s), you will be able to see a new file called A.class inside org/application/. Here is the new directory structure.

    .
    └── org
        └── application
            ├── A.class
            └── A.java

Now let’s try to run the compiled class file. If you are still at the root (i.e., if you compiled with the single-liner) then you can do:

    java org.application.A

If you are inside the application directory, then you might hope (if you are a java newbie like me) that something like java A which is cooked using the recipe of the command for running the programme at root will do the trick for you. Well, it won’t. To understand why, we need to learn a bit about the Java Classpath.

The Classpath (shorter name for class search path) is the path that the Java runtime environment (jre) searches for third party and user-defined classes. The default Classpath is the current directory. Java command-line tools like java, javac, javadoc allow programmers to set Classpath using -classpath option. Also, one can set system Classpath by setting the environment variable CLASSPATH in both Unix and Windows environments. If you have resources that need to be found by java tools very often, then it’s a good idea to put the path of those resources in the CLASSPATH variable.

Now, you might be able to figure out why we couldn’t run the compiled class inside the org/application directory. When we try to run it from there, Java runtime searches for org/application/ directory structure (remember: directory structure should match package definition) which is not found under org/application. When we run the programme from the root, Java runtime finds the expected directory structure so the programme gets executed with no exceptions.

So, what should we do if we want to run the programme inside org/application? We need to set the Classpath manually overriding the default value. Since the root directory is two levels above the current directory, we can use something like:

    java -classpath "../../" org.application.A

Compiling and running multiple files

Let’s move another step forward and try to compile a project with multiple files. Let’s create a new file called B.java which creates an object of type A inside its main method. That way, when we compile B, we will have to tell the Java compiler about the A class. Contents of B.java should look something similar to the following.

    package org.application;

    public class B {
        public static void main(String[] args) {
            A a = new A();
            System.out.println("Inside B.main()");
        }
    }

And below is the new directory structure.

    .
    └── org
        └── application
            ├── A.java
            └── B.java

If you are at the root, then compiling B.java file is no different from compiling A.java file. You can simply execute:

    javac org/application/B.java

Java runtime won’t have any problem in figuring out where the A.java is. While compiling B.java, it looks for A.class in the package org.application (which is the only known package to the compiler) which maps to the directory structure org/application/A.class. This is found below the root (or compiled from the respective .java file).

But what if you want to compile inside org/application? Now you can’t do javac B.java as before. Java compiler will look for org/application/A.class in the Classpath (which is the current directory) and well, there isn’t such a file. But you know what to do, right?

    javac -cp "../../" B.java

Note that -cp is a shorthand for -classpath.

Running the programme is the same as what we did with a single file.

Exercise: Try compiling two source files in different packages. For example, put class A in org.applicationA and B in org.applicationB.

Separating source and class files

After compiling B.java, your directory structure might look something similar to the following.

    .
    └── org
        └── application
            ├── A.class
            ├── A.java
            ├── B.class
            └── B.java

So we have our source files and output .class files from javac under the same root side by side. This is not desirable as it might clutter our source directory (imagine a project with hundreds, thousands of Java files). So, a good practice is to generate these files in a separate directory, say build under root. You can ask javac to do this using -d option. At root execute:

    mkdir build
    javac -d build org/application/B.java

Now the directory structure would look like something below.

    .
    ├── build
    │   └── org
    │       └── application
    │           ├── A.class
    │           └── B.class
    └── org
        └── application
            ├── A.java
            └── B.java

Note that, even though we asked javac to compile B.java, it has compiled A.java as well because Class A is used by Class B. What if we had already compiled A.java and have its A.class file? Recompiling it will be a waste. So let’s try to compile B.java using existing A.class file.

To do this, first remove the existing B.class file.

    rm build/org/application/B.class

Now let’s compile B.java file by setting the classpath to build directory so that javac can find the existing A.class file.

    javac -cp build/ org/application/B.java

You can read the articles listed under references for more information.

References

  1. http://kevinboone.net/classpath.html
  2. http://docs.oracle.com/javase/7/docs/technotes/tools/windows/classpath.html
  3. http://en.wikipedia.org/wiki/Classpath_%28Java%29