Java in The Command Line (Without IDE)

Overview

With the super power of modern IDEs, Java developers (maybe developers of other languages, too) don’t know how to work without them.

I don’t say this is bad but it’s cool to code without IDE (to understand the way Java compiler work, at a very basic level).

Compile a Java program and package to jar file

As you may already know, a jar file is actually a zip file. You can extract that file using unzip.

Let’s create a simple project that play as the role of a library. I’m going to write another program to import this library and call the library’s method.

package org.remote.lib;

public class Common {

    public static void fromLib() {
        System.out.println("Hello from lib");
    }
    
}

This file named Common.java is located under org/remote/lib.

The java library directory structure
The java library directory structure

Now, let’s compile this and package into a jar file.

Compile the library:

 javac org/remote/lib/Common.java

You should see no output after entering this command. That means the program was compiled successfully.

Let’s package the library in a jar file:

jar cf remote.jar org/remote/lib/Common.class

This will package the common library to a jar file named remote.jar, ready to be imported to other program, which I’m going to write next.

Write a Java program which import a Jar file

Let’s create another Java program in a different package. This time, it’s in com/local/java. The class name is TestJava.java

package com.local.java;
import org.remote.lib.*;

class TestJava {

    public static void main(String[] args) {
       System.out.println("hey from local"); 

       //Test faker
       System.out.println("Calling from imported lib:");
       Common.fromLib();
    }
}

As you can see, on line 2, I imported the library with full package name.

On line 11, I called the function from the library.

Let’s compile the TestJava class and try to run the program:

javac com/local/java/TestJava.java

Surprisingly, the compile will complain about missing package:

com\local\java\TestJava.java:2: error: package org.remote.lib does not exist
import org.remote.lib.*;
^
com\local\java\TestJava.java:11: error: cannot find symbol
       Common.fromLib();
       ^
  symbol:   variable Common
  location: class TestJava
2 errors

This error will not happen if you have both com/local/java and org/remote/lib sharing the same root. However, as I’m simulate the case where the library is compiled on a different server, the org and com directories should not be on the same parent directory.

To mitigate the problem, let’s add the remote.jar file in the classpath when compiling the TestJava program:

javac -cp remote.jar com/local/java/TestJava.java

This time, the compile didn’t complain and the program was complied just fine.

However, if you try to run the program now, you will get another error:

$ java com/local/java/TestJava
hey from local
Calling from imported lib:
Exception in thread "main" java.lang.NoClassDefFoundError: org/remote/lib/Common
        at com.local.java.TestJava.main(TestJava.java:11)
Caused by: java.lang.ClassNotFoundException: org.remote.lib.Common
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 1 more

It said that the class Common was not found under org/remote/lib.

When calling the program, you need to pass the classpath too.

So, this command will run the TestJava program just fine:

# This is for Windows
java -cp ".;remote.jar" com/local/java/TestJava

# This is for Unix/Linux
java -cp ".:remote.jar" com/local/java/TestJava

You will get the following output:

Program compiled and run with jar dependencies
Program compiled and run with jar dependencies

Notice that I passed “.;remote.jar” to the -cp in the command line. The dot(.) tell the JVM that current folder is in class path too. Without that (if you include just the jar file), you will get another NoClassDefFound error.

Transitive dependencies

Transitive dependencies are dependencies of dependencies. That means if the org.remote.lib.Common program above depends on another library (such as Lombok), you need to include Lombok jar in the classapth too.

This is where you miss tools like Maven/Gradle. They handle transitive dependencies for the developers.

Conclusion

As you can see, a simple program with no external dependencies is quite simple to build and run. However, things get complicated when there are imported libraries. Understand how to pass the libraries into the classpath helps you compile and run the program properly.

Leave a Comment