Picocli – Easiness for CLI arguments in Java

CLI (Command Line Interface) is the way how a program gets input when is executed from the command line or console.

It’s usual that the applications require to some additional parameters or arguments to be initialized. That entrypoint for Java (also Scala and Kotlin) applications is the famous public static main (String [] args) method, on it the arguments and options of the applications are indicated as an array of String elements, those arguments will be later interpreted by the application logic.

How to handle those argument in a really functional way (indexed, named parameters, optional parameters, default values, and more) following standards of UNIX, or POSIX will require a considerable effort. However, that’s why we have and can use Picocli.

Picocli, defines itself as the easiest way to create a rich command line application for JVM (and non JVM) environments. Let’s see a little bit more of this utility.

Getting started

Lets start by creating a simple example class, receiving three arguments and perform a math operation.

class MyApplication {
    public static void main(String... args) {
        
        if(args.length != 3){
          throw new IlegalArgumentException("3 arguments must be indicated");
        }
        
        Integer firstArg = Integer.parseInt(args[0]);
        Integer secondArg = Integer.parseInt(args[1]);
        String operator = args[2];
        
        if(operator == null || !(operator.equals("sum") || operator.equals("sub")){
          throw new IlegalArgumentException("operator argument should be sum or sub");
        }  
  	    new MyAplication().doLogic(firstArg,secondArg,operator);
    }
  
    public void doLogic(int a, int b, String operator){
            double result;
        switch (operator) {
          case "sum": 
       	    result = a + b; 
            break;
          case "sub": 
            result = a - b;
            break;
          default; 
            throw new IlegalArgumentException("Unexpected operation");
        }
        System.out.println("The result is "+result);
    }
}

The application then is called from command line with:

java -jar myjar.jar 2 3 "sum" 
The result is 5

We can see the dedicated effort to get, validate, and cast the arguments/parameters from the String array to the expected data that our application requires. And these are just basic validations, and don’t provide a rich and friendly interaction. So, let’s transform that code but using picocli at this time.

@Command(name = "myApplication", 
         mixinStandardHelpOptions = true, 
         version = "1.0",
         description = "Applies the indicated math operation to the arguments")
class MyApplication  implements Callable<Integer> {
    
    @Parameters(index = "0", description = "First argument of the operation")
    private int firstArg; 
  
    @Parameters(index = "1", description = "Second argument of the operation")
    private int secondArg; 
  
    @Option(names = {"-o", "--operator"}, description = "Operation to apply", default = "sum")
    private Operators operator;
    
    public enum Operators { sum, sub }
  
    public static void main(String... args) {
  	    int exitCode = new CommandLine(new MyApplication()).execute(args);
        System.exit(exitCode);
    }
  
    @Override
    public Integer call() throws Exception { // your business logic goes here...
         doLogic(firstArg, secondArg, operator); 
         return 0;
    }
  
    public void doLogic(int a, int b, Operators operator){
            double result;
        switch (operator) {
          case Operators.sum: 
       	    result = a + b; 
            break;
          case Operators.sub: 
            result = a - b;
            break;
          default; 
            throw new IlegalArgumentException("Unexpected operation");
        }
        System.out.println("The result is "+result);
    }
}

We can see that picocli is in charge of getting the parameters by us, validating the data (see the enum for example) and casting them to the expected datatype. Now, if we want to call the application we have many different ways to do it.

java -jar myjar.jar 2 3 -o sum 
The result is 5

java -jar myjar.jar 2 3 -o sub 
The result is -1

java -jar myjar.jar 2 3 --operator sum 
The result is 5

java -jar myjar.jar --operator sum 2 3
The result is 5

java -jar myjar.jar 2 3
The result is 5

For this operation we can use the commands -o , --operator , that value is optional, so if not indicated it will use a default value. In addition to that, the option -o can be indicated before or after any other arguments.

Another advantage provided by Picocli is that our class has a description field in the Command annotation, also the annotations Option y Parameter have it. Therefore it can generate automatically the documentation and help section accessible from command line for our application (without any extra effort) . So, the user can use the help command to understand how our application works and what it requires.

Options and Parameters

An @Option is an argument that must have one or more names. The names are case-sensitive, but that can be customized if required.

@Option(names = "-c", description = "create a new archive")
boolean create;

@Option(names = { "-f", "--file" }, paramLabel = "ARCHIVE", description = "the archive file")
File archive;

In addition to that, the options can be “interactive“, in other words, if the option is not indicated during the application call, Picocli will request it in the command line before starting your application logic. This is very useful for passwords.

@Option(names = {"-p", "--password"}, description = "Passphrase", interactive = true)
char[] password;

Any argument that is not a subcommand or an @Option is then considered as a “parameter”. The parameters are interpreted in posicional order, with indexed from 0 to N.

@Parameter(index = "0", description = "Operand 1")
int operand;

@Parameter(index = "1", description = "Message to display")
String message; 

Also, a range of indexes can be indicated, and it will be interpret and map as an array in our code.

@Parameter(index = "2..*", description = "Message to display")
File[] files;

Supported Data types

There is a big number of datatype supported out of the box, for most of the common uses we can face or required. They are validated by Picocli, so the don’t have to take care how to transform the String argument to our expected datatype value.

  • Any primitives Java type (boolean, short, char, int, double, float, long) and its wrappers.
  • Any enum
  • StringStringBuilderCharSequence
  • java.math.BigDecimaljava.math.BigInteger
  • java.nio.Charset
  • java.io.File
  • java.net.InetAddress
  • java.util.regex.Pattern
  • java.util.Date (in format "yyyy-MM-dd")
  • java.net.URLjava.net.URI
  • java.util.UUID
  • java.lang.Class 
  • java.nio.file.Path 
  • java.time object of the package: 
    • DurationInstantLocalDateLocalDateTimeLocalTimeMonthDayOffsetDateTimeOffsetTimePeriodYearYearMonthZonedDateTimeZoneIdZoneOffset 
  • java.sql.Connection (with the url pattern-> jdbc:subprotocol:subname)
  • and more data types.

In addition to that, customized data type converts can be created if required. Se more here

Adding Picocli to my code

If maven or gradle is used, the corresponding dependency can be added to the project.

dependencies {
    implementation 'info.picocli:picocli:4.6.1'
}
<dependency>
  <groupId>info.picocli</groupId>
  <artifactId>picocli</artifactId>
  <version>4.6.1</version>
</dependency>

Also, the creators of Picocli have created the utility as a Single class framework. They allow people to get the Command class from this git repository, and copy it into your code.

References

Leave a Reply

Your email address will not be published. Required fields are marked *