Apache Commons CLI

Introduction

The Apache Commons CLI is a library that provides API for parsing command-line options which we pass to programs. In this post, we will learn about the Apache Commons CLI to parse the command line arguments.

Command line options and arguments

We have been using command line arguments a lot, especially when using Unix commands. 

Example: ls -l
where, -l is a command line option we pass to the ls command.
 
Similarly, we can write programs that accept the command line arguments and the program does its work based on the values passed.
 
Example: We can pass command-line options to a Java program as
java Application -a 1 -b 2 -c 

After compiling Application.java and running it, we pass two command line options a and b with values 1 and 2, respectively.

Different forms of command line arguments

Usually the command line arguments can be passed using its short form or long form (representation). 

Example: To the ls command, we pass either -a or –all to not hide the hidden entries.

ls -a

ls --all

The above option (-a/–all) did not accept any arguments because the argument was a boolean.

If they accept an argument value, it looks like:
java Application -a ab --cacheSize=10
  • We passed a value of “ab” to the command line option a (short representation) 
  • We passed a value of 10 to the command line option cacheSize (long representation) 

Maven dependency on Apache Commons CLI

If you are using a Maven project, you can depend on Apache Commons CLI by adding the below dependency.

<dependency>
    <groupId>commons-cli</groupId>
    <artifactId>commons-cli</artifactId>
    <version>1.4</version>
</dependency>

Replace the version 1.4 with the latest available.

Apache Commons CLI – Terminologies

Let us look into the various steps or stages in processing a command line argument. There are three stages:

  • Definition
  • Parsing
  • Interrogation

Definition Stage

A command line option has a set of option + (optional) values for them. In the definition stage, we define what the allowed options are.
The Options class represents a collection of Option objects. We will look at ways to create an Option instance.

Parsing Stage

In this stage, we parse the command line arguments passed to the application with the above created Options instance. We use a CommandLineParser for this. The parse method of the CommandLineParser takes the Options and the command line arguments and returns a CommandLine object. 
If there is a problem parsing the command line options, this step fails with an exception.

Interrogation Stage

Now we have a CommandLine instance and the application uses this to query the argument values for the options.

Apache Commons CLI – A complete example

Let us look at using the Commons CLI library. The rest of the post dives deep into the various options for configuring the Option and building a CommandLine.

Definition Stage – Defining the Options

Options options = new Options();
Option option = new Option("t1", true, "test option 1");
options.addOption(option);

option = new Option("t2", "testOption2", true, "test option 2");
options.addOption(option);

We create two command line options called t1 and t2

  • For t1, we mandate it to accept an argument. 
  • For t2, we mandate it to accept an argument and also create a long representation named testOption2

Parsing Stage – Parsing the CL options into a CommandLine

Now, let us create a DefaultParser and parse command line arguments (String[] arguments). Calling the parse method on the built command line parser gives us a  CommandLine object.

CommandLineParser commandLineParser = new DefaultParser();
String[] arguments = new String[]{"-t1", "test-data", "--testOption2", "2"};
CommandLine commandLine = commandLineParser.parse(options, arguments);

Interrogation Stage – Querying the command line

We query the CommandLine object and check for the presence of command line options. If they are present, we can get the command line argument values for them.

System.out.println(commandLine.hasOption("t1"));
System.out.println(commandLine.getOptionValue("t1"));
System.out.println(commandLine.hasOption("testOption2"));
System.out.println(commandLine.getOptionValue("testOption2"));
System.out.println(commandLine.hasOption("t2"));
System.out.println(commandLine.getOptionValue("t2"));

Prints,

true
test-data
true
2
true
2

We can see that the second command line option can be queried by either the short representation or using the long form.

Creating Option instances using constructor

An Option is a single command-line option (example t1 in above example). It has a short name, long name, description of the option and a flag that describes if an argument is required for the option (an argument is not required for a boolean option). An option must either have a short or a long name.

To reduce the verboseness, I have created an utility method called process that accepts a String array of command line arguments (CL options and values) and an var-args of Options. 

  1. It creates an Options instance and adds all the passed Option instances.
  2. Creates a CommandLineParser
  3. Parses the arguments according to the specified options and returns the CommandLine instance.
public static CommandLine process(String[] args, Option... option) {
    CommandLineParser commandLineParser = new DefaultParser();
    Options options = new Options();
    Arrays.stream(option)
            .forEach(options::addOption);
    try {
        return commandLineParser.parse(options, args);
    } catch (ParseException e) {
        e.printStackTrace();
        throw new RuntimeException("Invalid arguments");
    }
}

We used Arrays#stream to create a stream out of the array.

Creating a boolean option

Let us create an option called c that represents whether to cache application data or not.

Option option = new Option("c", "Whether to cache");
String[] arguments = new String[]{"-c", "value"};

//call the utility method to build the CommandLine
CommandLine commandLine = process(arguments, option);

//Query the CommandLine for options
System.out.println(commandLine.hasOption("c")); //true
System.out.println(commandLine.getOptionValue("c")); //null

We used the two argument version of the Option constructor and passed the short name and the description of the option. By default the flag hasArg will be false, which means this option does not require an argument.

Since the command line option c was a boolean option, it did not have any argument and hence it prints null for the getOptionValue() call.

Option with arguments

There is an overloaded constructor which accepts a flag to denote whether the option takes an argument or not. We create an option named k (the key) and pass the value as “value”.

Option option = new Option("k", true, "The key");
String[] arguments = new String[]{"-k", "value"};

CommandLine commandLine = process(arguments, option);

System.out.println(commandLine.hasOption("k")); //true
System.out.println(commandLine.getOptionValue("k")); //value

Option with long name

In addition, we could also pass a long option name (–key).

Option option = new Option("k", "key", true, "The key");
CommandLine commandLine = process(arguments, option);

System.out.println("---");
arguments = arguments = new String[]{"--key", "value"};
//or
//arguments = new String[]{"--key=value"};

commandLine = process(arguments, option);

//can query by k or key
System.out.println(commandLine.hasOption("key")); //true
System.out.println(commandLine.getOptionValue("key")); //value 
System.out.println(commandLine.hasOption("k")); //true
System.out.println(commandLine.getOptionValue("k")); //value

Multiple Options

Below, we create two options k1 and k2, pass values for both, parse and query them.

Option option1 = new Option("k1", true, "First key");
Option option2 = new Option("k2", true, "Second key");
arguments = new String[]{"-k1", "value1", "-k2", "value2"};
commandLine = process(arguments, option1, option2);

System.out.println(commandLine.hasOption("k1")); //true
System.out.println(commandLine.getOptionValue("k1")); //value1
System.out.println(commandLine.hasOption("k2")); //true
System.out.println(commandLine.getOptionValue("k2")); //value3

Option – Setter methods

The Option class has some setter methods using which we can set some properties.

Setting argument name and required flag

Using Option#setRequired, we say that the command line option is mandatory. The setArgName sets a display name for the argument.

Option option = new Option("k", true, "The key");
option.setRequired(true);
option.setArgName("The key");
String[] arguments = new String[]{"-k", "value"};

CommandLine commandLine = process(arguments, option);
System.out.println(commandLine.hasOption("k")); //true
System.out.println(commandLine.getOptionValue("k")); //value

Setting an optional argument

For options that accept an argument value, there might be scenarios where the value must be optional (say when the application can resort to a default value). We set it using setOptionalArg setter method.

Option option = new Option("k", true, "The key");
option.setRequired(true);
option.setOptionalArg(true);
String[] arguments = new String[]{"-k"};

CommandLine commandLine = process(arguments, option);
System.out.println(commandLine.hasOption("k")); //true
System.out.println(commandLine.getOptionValue("k")); //null

Even though the option k requires an argument, the parsing succeeds since we have set the optionalArg to true.

Setting the type of Option

This is one of the most useful parameters we can set. When creating an Option, we can specify the type of that Option. In the below example, when creating the key option, we make the type of that Option as a Number.

Option option = new Option("k", true, "The key");
option.setRequired(true);
option.setType(Number.class);
String[] arguments = new String[]{"-k", "1"};

CommandLine commandLine = process(arguments, option);

The getOptionValue always returns the string representation of the passed argument value. To get the option value converted to the right type, call the getParsedOptionValue method. But it could throw a ParseException if the option value cannot be converted to the configured type.

System.out.println(commandLine.hasOption("k")); //true
System.out.println(commandLine.getOptionValue("k")); //1
System.out.println(commandLine.getParsedOptionValue("k")); //1

If we pass any other type other than a number for the k option, it will throw a ParseException.

Example:
String[] arguments = new String[]{"-k", "a"};
System.out.println(commandLine.getParsedOptionValue("k"));

//Throws
//Exception in thread "main" org.apache.commons.cli.ParseException: For input string: "a"

Option with more than one argument

By default, when we create an option with arguments, the number of arguments is default to one. There is a setter method called setArgs to which we can specify the number of arguments the option must take. This is useful for multi-valued command line options. 
The valueSeparator specifies how to extract the argument values from the string.

Let us create an option called v which must accept three values by using the value separator as a comma.

Option option = new Option("v", "The values");
option.setRequired(true);
option.setArgName("The values");
option.setArgs(3);
option.setValueSeparator(',');
String[] arguments = new String[]{"-v", "value1,value2,value3"};

CommandLine commandLine = process(arguments, option);

To get the values, call the getOptionValues method.

System.out.println(commandLine.hasOption("v")); //true
String [] cmdLineArgs = commandLine.getOptionValues("v");
System.out.println(Arrays.toString(cmdLineArgs)); //[value1, value2, value3]
System.out.println(cmdLineArgs.length); //3

If we pass less than three values, it throws a MissingArgumentException.

String[] arguments = new String[]{"-v", "value1,value2"};
CommandLine commandLine = process(arguments, option);

//Throws
org.apache.commons.cli.MissingArgumentException: Missing argument for option: v

If we pass more than three values, the rest of the values would be part of the last value and will not be split.

//passing more than 3
String[] arguments = new String[]{"-v", "value1,value2,value3,value4"};

CommandLine commandLine = process(arguments, option);

String[] cmdLineArgs = commandLine.getOptionValues("v");
System.out.println(Arrays.toString(cmdLineArgs)); //[value1, value2, value3,value4]
System.out.println(cmdLineArgs.length); //3

Note that the last value is “value,value4”.

Options constructor

The Options class has addOption methods that that mirror the Option class’s constructor. Hence, we can pass the values directly to the addOption method.

//boolean option
Options options = new Options();
options.addOption("c", "Whether to cache");
String[] arguments = new String[]{"-c", "value"};
CommandLineParser commandLineParser = new DefaultParser();
CommandLine commandLine = commandLineParser.parse(options, arguments);
System.out.println(commandLine.hasOption("c")); //true
System.out.println(commandLine.getOptionValue("c")); //nul

options = new Options();
options.addOption("k", true, "The key");
arguments = new String[]{"-k", "value"};
commandLineParser = new DefaultParser();
commandLine = commandLineParser.parse(options, arguments);

System.out.println(commandLine.hasOption("k")); //true
System.out.println(commandLine.getOptionValue("k")); //value
System.out.println();

options = new Options();
options.addOption("k", "key", true, "The key");
commandLineParser = new DefaultParser();
commandLine = commandLineParser.parse(options, arguments);

System.out.println(commandLine.hasOption("k")); //true
System.out.println(commandLine.getOptionValue("k")); //value
System.out.println(commandLine.hasOption("key")); //true
System.out.println(commandLine.getOptionValue("key")); //value

The disadvantage is that the other setter methods we saw are not accessible via this.

Option builder

The Option class has builder to build an Option instance. This is preferred over using a constructor and the setter methods. Some examples are shown below:

Option option = Option.builder("k")
        .longOpt("Key")
        .desc("The key")
        .build();

option = Option.builder("k")
        .longOpt("Key")
        .desc("The key")
        .optionalArg(true)
        .build();

option = Option.builder("v")
        .longOpt("value")
        .desc("The values")
        .numberOfArgs(3)
        .type(Number.class)
        .build();

Exceptions in processing command line arguments

Let us look at a few scenarios where exceptions could be thrown when processing the command line arguments.

Unrecognized Option

If we pass an option that is not defined, it throws an UnrecognizedOptionException.

Option option = new Option("c", "Whether to cache");
String[] arguments = new String[]{"-some", "value"};

process(arguments, "c", option);

//Throws
//org.apache.commons.cli.UnrecognizedOptionException: Unrecognized option: -some

Missing a mandatory option

Here we create two command line options k1 and k2; k2 is mandatory. But the command line arguments do not have k2 in it. It throws a MissingOptionException.

Option option1 = new Option("k1", true, "First key");
Option option2 = new Option("k2", true, "Second key");
option2.setRequired(true); //option2 is required

String[] arguments = new String[]{"-k1", "value1"};
CommandLine commandLine = process(arguments, option1, option2);

//Throws
//org.apache.commons.cli.MissingOptionException: Missing required option: k2

CommandLineParser

We saw that if the command line parser encounters an unrecognized option it fails by throwing an UnrecognizedOptionException. The parse method accepts a boolean flag called stopAtNonOption. If we pass true, then when it sees an unrecognized argument it stops the parsing and returns immediately. The remaining arguments (even if valid) are not processed.

Let us create two options k1 and k2. But in the command line argument we pass k1, k3 and k2 (in that order). By default, the parse method of the command line parser throws an UnrecognizedOptionException.

Option option1 = new Option("k1", true, "First key");
Option option2 = new Option("k2", true, "Second key");
String [] arguments = new String[]{"-k1" , "value1", "-k3", "value3", "-k2", "value2"};

Options options = new Options();
options.addOption(option1);
options.addOption(option2);

CommandLineParser commandLineParser = new DefaultParser();
CommandLine commandLine = commandLineParser.parse(options, arguments);

//Throws
//Exception in thread "main" org.apache.commons.cli.UnrecognizedOptionException: Unrecognized option: -k3

Let us pass true for the flag stopAtNonOption.

CommandLine commandLine = commandLineParser.parse(options, arguments, true);

System.out.println(commandLine.hasOption("k1")); //true
System.out.println(commandLine.getOptionValue("k1")); //value1
System.out.println(commandLine.hasOption("k2")); //false

It has successfully processed the command line option k1. But when it encountered k3, it returned without processing k2 (a valid option).

Other CommandLine methods

We already saw a few methods of the CommandLine class. Let us look at a few more.

getArgList and getArgs

The getArgList method returns any left-over options and arguments. This happens in the above case when we passed true to stop and return the processing when it encounters a bad/unrecognized option.
The getArgs method does the same but returns as a String array (rather than a List).

System.out.println(commandLine.getArgList()); //[-k3, value3, -k2, value2]
System.out.println(Arrays.toString(commandLine.getArgs())); //[-k3, value3, -k2, value2]

Default Option Value

The getOptionValue has an overload where we can pass a default option value in case the passed option does not have any parsed argument value. This can be used along with the three argument parse method of the command line parser.

System.out.println(commandLine.getOptionValue("k2", "k2-default")); //k2-default
System.out.println(commandLine.getOptionValue("k3", "k3-default")); //k3-default

Conclusion

We started by looking at the various stages of command line parsing involved with the Apache Commons CLI. We saw an end to end example of defining options, parsing command line arguments and retrieving the option values. Then, we saw various ways to create Option instances and methods on the CommandLine object.

References

Commons CLI page

Leave a Reply