Command Line Interface in Java : API overview

Why using the Command Line Interface ?

  • It’s faster to create prototype with. At least when the UI is not the prototype you’re trying to show.
  • It’s easier to debug
  • More powerful than UI who don’t implement all the options in order to keep the UI simple and usable. It’s just a matter of UX.
  • Sometimes you have to. It’s the case if you’re running a linux server.

Working with the CLI is not always the right option and there is also advantage of using an UI

Executing a jar in Command Line

When compile in an executable java jar you can simply execute it from the command line.

java -jar mon-exec.jar

In order to send some parameters:

java -jar mon-exec.jar aStringParameter

In this example the string “aStringParameter” is sent to the variable “args” in the Main class.

package com.doduck.prototype.cli;
public class Main {
	public static void main(String[] args) {
		System.out.println("first argument => "+ args[0]);
	}
}

So the command abouve would display:

first argument => unParamEnString

All the challange is to find an handy way to interprete the strings sent into the “String[] args” parameter.
In order to do so when come up with few solution.



CLI API in Java

Download the source code of those test on github
You also need to learn how to create an executable jar file.

Apache commons CLI

A reference as it’s a Apache project. The API is some how limited but on most project this will be enough.

	public static void main(String[] args) {
		Options options = new Options();
		options.addOption("myCmd", "myCommand", false, "will run myCommand()." );
		options.addOption("helloW", "helloWord", true, "display hello word the number of time specify." );

		try{
			CommandLine line = new BasicParser().parse( options, args );

			if( line.hasOption( "myCommand" ) ) {
				myCommand();
			}

			if(line.hasOption("helloWord")){
				String repeat = line.getOptionValue("helloWord");
				Integer repeatInt = new Integer(repeat);
				for(int i =0; i<repeatInt; i++){
					System.out.println( "Hello word !");
				}
			}

		}catch( ParseException exp ) {
		    System.out.println( "Unexpected exception:" + exp.getMessage() );
		}
	}

	public static void myCommand(){
		System.out.println("myCommand() just get call");
	}


For

  • Apache project. A reference.
  • Simple
  • POSIX,GNU,Java like options
  • Group options




Against

  • To simple, weak in option
  • String argument only


jopt-simple

Jopt-simple as is name say try to be as simple as possible.
You can define the CLI in one line:

public static void main(String[] args) {
	OptionParser p = new OptionParser("fc:q::");
	p.accepts("ftp" );
	p.accepts("user").requiredIf("ftp").withRequiredArg();
	p.accepts("pass").requiredIf("ftp").withRequiredArg();
	
	OptionSet opt = p.parse(args);
	if(opt.has("f")){
		System.out.println("-f exist");
		System.out.println("-f with : "+ opt.valueOf("f"));
	}
	if(opt.has( "c" )){
		System.out.println("-c exist with:"+opt.valueOf("c"));
	}
	
	if(opt.has("ftp")){
		// we know username and password
		// existe if -ftp is set
		String user = (String) opt.valueOf("user");
		String pwd = (String) opt.valueOf("pass");
		System.out.println("user:"+user+" pass:"+pwd);
	}
}

The String “fc:q::” define the CLI parser.
-f is optional
-c is mandatory
-q is as list of mandatory parameters

-ftp is optional
-user is mandatory if -ftp is set
-pass is mandatory if -ftp is set

When jopt-simple go more complexe it’s still quite simple to understand.

OptionParser p = new OptionParser();
OptionSpec<File> f = p.accepts("file").withRequiredArg().ofType(File.class).defaultsTo(tempFile);


For

  • Simple, intuitif
  • Powerful
  • Good documentation
  • Alternative to groups




Against


naturalcli

Nice idea. Making the CLI understandable to the humain.

Here is an examples in order to create a command like:

send file /home/doduck/myFile.txt to ftp://ftpsrv/dir notify theBoss with "Please, see myFile.txt on the ftp"

Here is the Java code:

	public static void main(String[] args) throws ExecutionException, InvalidSyntaxException {
		
		ICommandExecutor sendFile = new ICommandExecutor ()
        {
           public void execute(ParseResult pr) throws ExecutionException 
           {
        	   String file = (String) pr.getParameterValue(0);
        	   String server = (String) pr.getParameterValue(1);
        	   String userName = (String) pr.getParameterValue(2);
        	   String msg = (String) pr.getParameterValue(3);
        	   
        	   // TODO send the file to the server...
        	   
        	   System.out.println("the file '"+file+"' was sent sucessfully"); 
        	   System.out.println("to '"+server+"' and the user '"+userName+"'");
        	   System.out.println("get notify with the message '"+msg+"'");
           }
        };
		
		Command cmd = new Command("send file <file:string> to <server:string> notify <username:string> with <msg:string>",
			 "Send a file to a server with notification message", 
			 sendFile);

		
	    Set<Command> cs = new HashSet<Command>();
	    cs.add(cmd);
	    new NaturalCLI(cs).execute(args);
    }


For

  • Easy to read
  • Easy to remember the CLI




Against

  • Complexe type difficult to use
  • Difficult API
  • No Maven repertory


JCommando

Very different API. It will allow you to create dependancy into your commandes.
If param A is set the param B will be mandatory.
It become easy to create multiple commandes and dependency between the arguments.

In a first time let’s design our commande line in XML:

<?xml version="1.0" encoding="UTF-8"?>
<jcommando>
   <option id="sendFtp" long="sendFtp" short="sendFtp">
      <description>send file by ftp</description>
   </option>
   <option id="debugLever" long="debugLever" short="d" type="long">
      <description>debug level</description>
   </option>
[...]
   <commandless id="execute" allow-optionless="true">
   	<and>
   		<option-ref id="debugLever" />
		   <xor>
            <and>
   		    	<option-ref id="sendEmail" />
   		    	<option-ref id="smtp" />
   		    	<option-ref id="login" />
               <option-ref id="pwd"/>
            </and>
		    	
            <and>
               <option-ref id="sendFtp" />
   			   <xor>
   		    		<option-ref id="isFtp"/>
   		    		<option-ref id="isSftp"/>
   		    	</xor>
   		    	<option-ref id="host"/>
   		    	<option-ref id="login"/>
   		    	<option-ref id="pwd"/>
            </and>
         </xor>
		</and>
   </commandless>

</jcommando>

Then thanks to a Ant command we can generate our class who will do the parsing for use.
In order to execut the project please read look at our prototype project on github

The generated class could also been created by hand:

public abstract class Cli extends JCommandParser
{
   /**
     * JCommando generated constructor.
     */
   public Cli()
   {
      Option sendFtp = new Option();
      sendFtp.setId("sendFtp");
      sendFtp.setShortMnemonic("sendFtp");
      sendFtp.setLongMnemonic("sendFtp");
      sendFtp.setDescription("send file by ftp");
      addOption(sendFtp);

      Option isFtp = new Option();
      isFtp.setId("isFtp");
      isFtp.setShortMnemonic("isFtp");
      isFtp.setLongMnemonic("isFtp");
      isFtp.setDescription("isFtp");
      addOption(isFtp);
   [...]

Next you have to implement this generated class as it is abstract.

public class MyCLI extends Cli{

	private boolean isSendByFTP;
	private long debugLevel = 0;
	private boolean isSFtp;
	private String host;
	private String login;
	private String password;
	[...]

	@Override
	public void setSendFtp() {
		this.isSendByFTP = true;
	}
	@Override
	public void setIsFtp() {
		this.isSFtp = false;
	}
	@Override
	public void setIsSftp() {
		this.isSFtp = true;
	}
	@Override
	public void setHost(String host) {
		this.host = host;
	}
	
	@Override
	public void setLogin(String login) {
		this.login = login;
	}
	
	@Override
	public void setPwd(String pwd) {
		this.password = pwd;
	}

	@Override
	public void doExecute() {
		if(isSendByFTP){
			String protocol = this.isSFtp ? "SFTP" : "FTP";
			
			System.out.println("send a file by FTP");
			System.out.println("over the protocole "+protocol);
			System.out.println("host:"+this.host);
			System.out.println("login:"+this.login);
			System.out.println("password:"+this.password);
			
		}else if(isSendEmail){
			[...]
		}
	}
[...]
}

Finally we run the command in the main:

public class Main {
    public static void main(String[] args) {
	MyCLI cli = new MyCLI();
	cli.parse(args);
    }
}


For

  • Easy to create dependancy (AND, OR, XOR, NOT)
  • Good for complex CLI / big number of command


Against

  • Only one message get back for a bad command “Incompatible options specified.”
  • Only primitive type
  • Ant command only
  • No Maven repo
  • Complexe


Jcommander

Very interesting solution using annotation. Perfect in order the keep your code clean.

public class UploaderCommand {
	@Parameter(names = "-debug", description = "Debug mode")
	private boolean debug = false;

	@Parameter(names = "-file", description = "file to send", required = true)
	private String file = null;
	
	@Parameter(names = "-srv", description = "server to send the file to", required = true)
	private String srv = null;

	//[...Getter Setter...]
}
	public static void main(String[] args) {
		UploaderCommand uploader = new UploaderCommand();
		new JCommander(uploader, args);

		System.out.println("sending file: "+uploader.getFile());
		System.out.println("to srv: "+uploader.getSrv());
		System.out.println("debug enable: "+uploader.isDebug());
	}


Pour

  • Clean code
  • annotation
  • easy




Contre


clajr

Good idea for prototyping.
You just need to define your function name correctly. Then you can call them thanks the the reflexion.
Using Clajr is limited for the command line point of view. But the source code could be twiste and use for some many different project !

	public static void main(String[] args) throws Throwable{
		CLAJR.parse(args, new Action());
    }
public class Action {

	public void _i__info(){
		System.out.println("Here is some information");
	}
	public void _p__print(String toPrint){
		System.out.println("let's print =>"+toPrint);
	}

}

The class Action can be call thanks to the commande

java -jar mon-exec.jar -i -p "hello world"

And it will display in the console:

Here is some information
let's print =>hello world


For

  • Original
  • Only one class
  • Can fit more than the CLI




Against

  • Less powerful than alternative solution (for CLI)


jewelcli

Jewelcli look a lot like JCommander.
Using annation the code stay simple and graceful.

Jewelcli is very good library. It documentation is well done, it offer lot of possibilites. I simply miss a lot of time for testing it more in detail.

Jargs

Simple and graceful.

JSAP

“Yet an other CLI library”.
Traditionelle.

args4j

Use annotation in order to parce the command line.
Similar to Jewelcli et JCommander using generics.

parse-cmd

Parse-cmd is different from the other only with the regex possibility.

And there is still much more API


Real case exemple

Checkout a real case example developping a Dropbox App in Java using the CLI to control it.
It’s a usefull little software you can use to pass big files from your computer to your server.

Conclusion

There is a lot of librarie who solve the CLI complexity problem.
Recommanding an library over an other don’t make sense. It depend to much of what you’re looking for.
Take the time to test, prototype, overview the documentation in order to find what library fit your well.

  • Frank Loizzi

    Great article! Thanks for your effort. I’m not the only one who is struggling with the CLI… :)

  • Axel Stoll

    Thank you very much :)

  • Zin Minn

    Very nice post !!!! Thank you so much :-)

  • Balint Gyapjas

    This is a very exhaustive overview, still it was so easy to find the one that I want to use. Thanks a bunch.