DEV Community

Andy Gherna
Andy Gherna

Posted on

Writing Java Command Line Tools is Cumbersome... Or is it???

Simple Java Tools

The JDK comes with a lot of great command-line tools on its own that support development. But on every project I've ever done I find I need just a little more from a tool that I can't get from it which usually means I have to write my own. Every tool I write is for me and it's usually just enough to do the job.

I write a lot of command-line tools to do my extra work. Most Java developers I work with hate the idea of writing a command-line Java tool. They have to be compiled, then they have to be run. Using Maven helps with the exec:java goal. Without Maven, you're writing long javac commands to compile, then a long java command to run it. But if you're careful and stick to what's in the JDK to write a small tool to get the job done, you can use a trick with the shell I saw a few years ago to make writing Java a lot more like writing Python.

A Simple Bash/Java Example

Previously, I mentioned that I'm a huge fan of Bash. All you need is a single Java source file and you can write something useful very fast. Consider this source file:

/*bin/mkdir -p /tmp/.java/classes 2> /dev/null

# Compile the program.
#
javac -d /tmp/.java/classes $0

# Run the compiled program only if compilation succeeds.
#
[[ $? -eq 0 ]] && java -cp /tmp/.java/classes $(basename ${0%.*}) "$@"
exit
*/

class SimpleGreeting {
    public static void main(String... args) {
        System.out.println("Hello");
    }
}
Enter fullscreen mode Exit fullscreen mode

Running this in the shell is as easy as:

$ bash SimpleGreeting.java
Hello
$
Enter fullscreen mode Exit fullscreen mode

What happened here? Why was that so easy?

The answer is in the comment at the top. Shell scripts often start with a shebang interpreter directive. A Java source file with this comment at the top runs the commands in bash.

Breaking down the Interpreter Directive

It's easy to see what's going on here:

  • Create a subdirectory under /tmp redirecting output to /dev/null if the directory is already there (we don't care about an error message).
  • Compile the source file and write the .class file in the target directory we just made.
  • Run the class file from the directory it was just written to, stripping off the suffix of the source file. But only run it if it compiled.
  • Exit the process with the return code of the java run.

The best part about writing tools this way is that you can make changes to the code and "run" it again without worrying if compilation needs to happen. If you have an error, the compiler will tell you where it is and it won't attempt to run your program.

Tips

Developing tools this efficiently means you should:

  • Use as few dependencies as possible outside the JDK (ideally none)
  • Keep your tools SIMPLE, short and in 1 source file.
  • Run from the command line using bash SourceFile.java <args>.
  • Avoid too much logic in the interpreter directive; but don't be afraid to use logic if you have to.

Sample Tools

I've written some tools that have helped me out tremendously:

  • Serving javadoc from the JDK docs zip file and javadoc jar files in your local Maven repository
  • Dumping Base64/raw secret keys from keystores
  • Dumping a private key from a keystore
  • Importing externally generated secret key into a keystore
  • Checking LDAP/JDBC connectivity

There are tons of things you can do quickly using just the JDK. It may not have all the batteries included but it has all the kinds you could find without having to go to a specialty store.

Acknowledgement

I'd like to again acknowledge Jessie Wilson whose gist for Rip.java (linked above) inspired this post. He's a smart dude who likes to get things done quickly (obviously).

My examples are in my Dotfiles GitHub repository. Look in the Scripts directory and you'll find my tools along with all the other things I use.

Oldest comments (2)

Collapse
 
phlash profile image
Phil Ashby • Edited

This is probably going in very much the wrong direction, however it's Friday & I was inspired to write a more complete self-executable script header for Java, much like the old install.sh hacks that bundled a tar file with it's install script...

gist.github.com/phlash/a5e914430bf...

A much better approach might be adopting JShell: en.wikipedia.org/wiki/JShell, or adding specific Java magic to en.wikipedia.org/wiki/Binfmt_misc.

I apologise in advance for those offended and screaming 'My eyes!'...

Collapse
 
argherna profile image
Andy Gherna

I get a lot of little important things done quickly using techniques like that. Nothing wrong with it.