Saturday, 2 May 2009

Running a Java Program from the Command Line: NCDF "Exception"

This is the most rudimentary task every Java newbie struggles with (and may take hours to resolve!) This post will aim to delve into the knowledge needed to succeed in this simple task, which requires a surprising amount of knowledge.

Having written (and compiled) a simple Java program how do we run it from the command-line? How many times has one tried:

java classname

only to be greeted by the all-too-familiar java.lang.NoClassDefFoundError. In an enterprise environment this error is almost never seen, since virtually every company running a large Java infrastructure implements a run script to solve all the

* issues of classpath
* package names/relative paths (the tight coupling between directory names and package names is a particular source of initial frustration and bewilderment when running Java programs for the first time).

This contrasts heavily with the relative simplicity of compiling and running C programs.

Java programs, as we know are compiled into class files. Suppose I have written a Java game called DinoGame that is compiled into DinoGame.class, which is stored in a directory called dinosaurs. To view contents of the DinoGame class, I do the following:

* cd dinosaurs
* javap DinoGame (NOT javap DinoGame.class)

This will tell me: "O i've been compiled from DinoGame.java and here are my methods". One of my methods is public static void main which takes java.lang.String[] (1-d array) i.e. the output of javap will always display fully-qualified package names.

Now I try running DinoGame by typing: java DinoGame from the dinosaurs directory. Oh dear! java.lang.NoClassDefFoundError: Could the find the main class: DinoGame. Program will exit. However!! java provides us with a useful hint (wrong name: dinosaurs/DinoGame). The java interpreter sees the class file in the current directory but it won't be able to execute it because it has the wrong name.

* cd out of the dinosaurs directory
* java -classpath . dinosaurs/DinoGame OR java -cp . dinosaurs.DinoGame (again NOT dinosaurs.DinoGame.class)

The -classpath . (or -cp .) is needed here since there are no class files in the current directory, so there are no reference points for the Java interpreter to use in generating useful error messages. To infer a problem such as the lack of a fully qualified classname would require the Java interpreter (run from a specific directory) to recursively search all subdirectories from the current directory which
could be very inefficient.

The tricky thing here is you need to think in terms of fully qualified package names. As we have seen from javap output, this is what the interpreter uses to refer to class files.

Now even if you get the above sequence correct (java -cp . class-with-fully-qualified-package-name), if you have a broken java.exe process running in the background (at least in Windows environment) whatever sequence of characters you type may not work and still crash with NCDF Exception (ok, ok I know it's written as NoClassDefFound ERROR, but what triggers it is the java.lang.ClassNotFound EXCEPTION, so for conciseness let's refer to it as NCDF EXCEPTION, since an exception is the underlying generator of this error).

Another useful point to remember is whenever you see a .java or .class file, remember that the Java toolset invariably requires you to OMIT the .java or .class extension when using your Java files as arguments to the tools (read the code in openJDK to see how many times String.concat(".class") appears! Forgetting this simple rule will result in frustration!

It is worth delving a little into the rather ungainly stack trace that appears when you get an NCDF error. It gives some insight into the core packages of Java (not just java.lang). It shows a tight interplay between java.security and java.net packages.

NCDF error is generated by CNF exception which is generated by java.net.URLClassLoader$1.run(Unknown source). The java.net.UCL will provide a starting point for some interesting analysis.

java.net.UCL (see source)is implemented by David Connelly (around 600 lines of code, well-worth reading, every single line provides essential knowledge) was introduced in Java 2 and implements the generified findClass method (generified in the sense it returns a Class object of wildcarded type); as a contextual note, remember generics were only introduced in Java 5. UCL.findClass is the fella that winds up throwing the "class not found" exception. It is effectively one call into native code: AccessController.doPriviliged(privileged action, created as an anonymous class).

This stream of events begins with java.lang.ClassLoader. method loadClassInternal (a synchronized method invoked by the virtual machine to load a class) which calls auxiliary methods loadClass(String name)->loadClass(String name, bool resolve) (classes must be resolved before they can be used). What does it mean to resolve a class, I hear you ask? Read the "Execution" chapter of the Java Language Specification. Resolution involves resolving symbolic references to other classes by loading thoses classes and checking the references are valid. Resolution is optional at the time of initial linkage.

Footnote:
Java security comprises a very large set of APIs and if you want a lowdown your best bet is right here).

No comments: