Simplifying Command Line Processing

Introduction

I like many of you have spent many hours using Apache Commons CLI to create command line options. It does a great job. As the number of options, or groups increases, the framework begins to show its rough edges. A tool called JCommander which I mentioned in my post Useful Java Frameworks really begins to shine. I will present two versions of the same command line processing, and let you decide.

CLI.java

    package com.bluelotussoftware.cli;
    
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import org.apache.commons.cli.AlreadySelectedException;
    import org.apache.commons.cli.CommandLine;
    import org.apache.commons.cli.CommandLineParser;
    import org.apache.commons.cli.DefaultParser;
    import org.apache.commons.cli.HelpFormatter;
    import org.apache.commons.cli.Option;
    import org.apache.commons.cli.OptionGroup;
    import org.apache.commons.cli.Options;
    import org.apache.commons.cli.ParseException;
    
    /**
     *
     * @author <a href="mailto:jyeary@bluelotussoftware.com">John Yeary</a>
     * @version 1.0.0
     */
    public class CLI {
    
        private static final Options OPTIONS;
    
        static {
            OPTIONS = CLI.createOptions();
        }
    
        public static void main(String[] args) {
    
            CommandLineParser commandLineParser = new DefaultParser();
    
            try {
                CommandLine cmd = commandLineParser.parse(OPTIONS, args);
    
                if (cmd.getOptions().length == 0) {
                    CLI.help();
                    return;
                }
    
                if (cmd.hasOption('v')) {
                    System.out.println("Product Version: 1.0.0");
                }
    
                if(cmd.hasOption('i')) {
                    System.out.println("Starting interactive mode.");
                }
    
                if(cmd.hasOption("start")) {
                    System.out.println("Starting application");
                }
    
                if(cmd.hasOption("stop")) {
                    System.out.println("Stopping application");
                }
    
    
            } catch (AlreadySelectedException e) {
                CLI.help();
                Logger.getLogger(CLI.class.getName()).log(Level.SEVERE, null, e);
            } catch (ParseException e) {
                CLI.help();
                Logger.getLogger(CLI.class.getName()).log(Level.SEVERE, null, e);
            }
        }
    
        private static void help() {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("CLI", OPTIONS);
        }
    
        private static Options createOptions() {
            Options options = new Options();
    
            OptionGroup mode = new OptionGroup();
    
            Option help = new Option("help", "Help");
    
            Option interactive = Option.builder("i")
                    .longOpt("interactive")
                    .desc("Starts the processor in interactive mode.")
                    .build();
    
            Option start = Option.builder().longOpt("start")
                    .desc("Start the application.")
                    .build();
    
            Option stop = Option.builder().longOpt("stop")
                    .desc("Stop the application.")
                    .build();
    
            Option properties = Option.builder("p")
                    .longOpt("properties")
                    .desc("List all properties")
                    .build();
    
            Option quit = Option.builder("q")
                    .longOpt("quit")
                    .desc("Quit the application.")
                    .build();
    
            Option version = Option.builder("v")
                    .longOpt("version")
                    .desc("The current version of the application.")
                    .build();
    
            mode.addOption(interactive);
            mode.addOption(start);
            mode.addOption(stop);
    
            options.addOptionGroup(mode);
            options.addOption(help);
            options.addOption(properties);
            options.addOption(quit);
            options.addOption(version);
    
            return options;
        }
    }
usage: CLI
 -help              Help
 -i,--interactive   Starts the processor in interactive mode.
 -p,--properties    List all properties
 -q,--quit          Quit the application.
    --start         Start the application.
    --stop          Stop the application.
 -v,--version       The current version of the application.

JCommanderCLI.java

    package com.bluelotussoftware.cli;
    
    import com.beust.jcommander.JCommander;
    import com.beust.jcommander.Parameter;
    
    /**
     *
     * @author <a href="mailto:jyeary@bluelotussoftware.com">John Yeary</a>
     * @version 1.0.0
     */
    public class JCommanderCLI {
    
        @Parameter(names = {"-v", "--version"}, description = "The current version of the application.")
        private boolean version;
        @Parameter(names = {"-h", "--help"}, description = "Provides help to the user.", help = true)
        private boolean help;
        @Parameter(names = {"-q", "--quit"}, description = "Quit the application.")
        private boolean quit;
        @Parameter(names = {"-i", "--interactive"}, description = "Starts the processor in interactive mode.")
        private boolean interactive;
        @Parameter(names = "--start", description = "Start the application.")
        private boolean start;
        @Parameter(names = "--stop", description = "Stop the application.")
        private boolean stop;
        @Parameter(names = {"-p", "--properties"}, description = "List all of the configured properties.")
        private boolean props;
    
        public static void main(String[] args) {
    
            JCommanderCLI clic = new JCommanderCLI();
            JCommander jc = JCommander.newBuilder()
                    .addObject(clic)
                    .build();
            jc.parse(args);
            jc.setProgramName(JCommanderCLI.class.getCanonicalName());
    
            if (args.length == 0) {
                jc.usage();
                return;
            }
    
            clic.run();
        }
    
        public void run() {
    
            if (version) {
                System.out.println("CLICommander version 1.0.0");
            }
    
            if (interactive) {
                System.out.println("Starting interactive mode.");
            }
    
            if (start) {
                System.out.println("Starting application");
            }
    
            if (stop) {
                System.out.println("Stopping application");
            }
        }
    }
Usage: com.bluelotussoftware.cli.JCommanderCLI [options]
  Options:
    -h, --help
      Provides help to the user.
    -i, --interactive
      Starts the processor in interactive mode.
      Default: false
    -p, --properties
      List all of the configured properties.
      Default: false
    -q, --quit
      Quit the application.
      Default: false
    --start
      Start the application.
      Default: false
    --stop
      Stop the application.
      Default: false
    -v, --version
      The current version of the application.
      Default: false

Results

If you have come to the same conclusion as me, then you may want to simply include JCommander in your next development project. If you are already invested in Commons CLI, you can spend the time to convert it, but don’t waste time if you are not encountering issues.

The Maven dependency can be found here:

<dependency>
  <groupId>com.beust</groupId>
  <artifactId>jcommander</artifactId>
  <version>1.72</version>
</dependency>