MainArgsHandler

A Java utility class which processes the command-line arguments used to startup a Java application.

What is MainArgsHandler?

Being someone who hates having to solve the same problem over and over again, I always found it tedious to have to write code to check the command-line values passed to the main method of a Java application. So I wrote the utility class MainArgsHandler to automate as much of the data validation process as possible. MainArgsHandler offers the following features in handling command-line arguments to a Java application:

  1. You (the developer) can declare which command-line flags and variables are accepted by your application.
  2. You can specify how many times a command-line variable can (or must) be provided.
  3. For each command-line variable you can specify a Pattern which defines what values are valid.
  4. With a single method call the command-line arguments are checked against your declared rules, and an IllegalArgumentException is thrown if invalid flags, variables or values have been provided.
  5. A method is provided which produces a String summary of all of the command-line flags and variables accepted by your application, so you can display it to the user if they need assistance in running your application.
  6. When valid arguments have been provided, methods allow you to check whether a particular flag or variable has been provided, and to get a List of values provided for each command-line variable.

The source code for MainArgsHandler is full of JavaDoc comments which give full technical details about the use of each method, so this page will act as a short introduction to using MainArgsHandler. You can find the source code in the uk.org.bobulous.java.startup repository if you want to investigate further.

A simple MainArgsHandler example

Suppose you have a Swing application which adds an icon to the system notification area (often wrongly called the system tray) and you want to allow end-users to request that your application startup minimized so that on loading the icon appears in the notification area but no windows are opened. The easiest way to do this is to allow your application to accept a command-line argument, so that your application is run from the command-line with a call that looks something like this:

java SwingApplication --minimize-to-notification-area

We can use MainArgsHandler to check for this command-line flag. A flag is a value which is either present in the command-line arguments ("on") or not present ("off"). A flag does not take any values; it simply toggles a feature on if it's found in the command-line arguments. Writing the code to tell MainArgsHandler to check for this is simple:

public static void main(String[] args) {
    MainArgsHandler handler = MainArgsHandler.getHandler();
    handler.permitFlag("minimize-to-notification-area");
    handler.processMainArgs(args);
    if(handler.foundFlag("minimize-to-notification-area")) {
        // Tell your Swing application to startup minimized.
    }
    // Rest of your application main method continues as usual.
}

And that's it. The MainArgsHandler instance is told to allow a flag called "minimize-to-notification-area", then it's told to process the actual arguments passed from the command-line to the main method, then it's asked whether or not those arguments contain the flag "minimize-to-notification-area". If the flag is found then you can tell your Swing application to start off in the notification area.

Command-line flags are always optional, but they can only be passed to your application if you have declared them to MainArgsHandler using the permitFlag method. If a flag is passed to your application which is not permitted then MainArgsHandler will throw an IllegalArgumentException to indicate that your application has been called with invalid command-line arguments. So it's probably best to wrap the call to processMainArgs in a try-catch block so that you can tell the user they have made a mistake if this happens. Something like this:

try {
    handler.processMainArgs(args);
} catch(IllegalArgumentException iae) {
    System.out.println("You provided invalid arguments to this application. " +
            "Please see the command-line usage:");
    System.out.println(handler.getUsageSummary());
}

The getUsageSummary method produces a String which contains a full list of command-line flags and variables which are accepted by your application, so the end-user can see what the options are.

Handling command-line variables with MainArgsHandler

Suppose you have written a Java application which produces a compressed output file from a set of source files, and you need to be able to pass to this application a list of source files, an output file path, an optional output format, and flags to enable additional settings. Calling this application on the command-line might look like this (with line breaks added to make it easier to read on this webpage):

java CompressFiles --source=/home/bob/File_one.txt \
    --source="/home/bob/File two.txt" \
    --output-file=~/filepack.tar.gz \
    --force-overwrite --output-format=tarball

This is easy to do using MainArgsHandler and the code might go something like this:

public static void main(String[] args) {
    // Define a few Interval<Integer> instances for convenience.
    Interval<Integer> ZERO_OR_ONE = new GenericInterval<Integer>(0, 1);
    Interval<Integer> ONE_EXACTLY = new GenericInterval<Integer>(1, 1);
    Interval<Integer> ONE_OR_MORE = new GenericInterval<Integer>(1, null);
    // Define a Pattern which explicitly states which values are
    // acceptable for the command-line variable "output-format".
    Pattern OUTPUT_FORMAT_PATTERN = Pattern.compile("(?:zip|tarball)");
    // Get the sole instance of MainArgsHandler and then declare the
    // command-line variables and flags which this Java application accepts.
    MainArgsHandler handler = MainArgsHandler.getHandler();
    handler.permitVariable("source", ONE_OR_MORE,
            "The path to a source file to be added to the compressed file.");
    handler.permitVariable("output-file", ONE_EXACTLY,
            "The path of the compressed output file.");
    handler.permitVariable("output-format", ZERO_OR_ONE,
            "The compressed format which should be used to create the output " +
                    "file. Can be either zip or tarball. Defaults to zip if " +
                    "neither is explicitly provided on the command-line.",
            OUTPUT_FORMAT_PATTERN);
    handler.permitFlag("force-overwrite",
            "Overwrite the output file if it already exists.");
    handler.permitFlag("skip-missing-source-files",
            "Continue regardless if a source file is not found.");
    // Now that all command-line variables and flags have been
    // declared, call the processMainArgs method and be prepared for an
    // IllegalArgumentException to be thrown if the command-line arguments
    // passed to this application do not follow the declared specification.
    try {
        handler.processMainArgs(args);
    } catch(IllegalArgumentException iae) {
        System.out.println("You provided invalid arguments to this application. " +
                "Please see the command-line usage:");
        System.out.println(handler.getUsageSummary());
    }
    if(handler.foundFlag("force-overwrite")) {
        // Tell your application to overwrite the output file without warning.
    }
    if(handler.foundFlag("skip-missing-source-files")) {
        // Tell your application to skip missing source files without warning.
    }
    List<String> sourceFileList = handler.getValuesFromVariable("source");
    String outputFile = handler.getValuesFromVariable("output-file").get(0);
    String outputFormat;
    if(handler.foundVariable("output-format") {
        outputFormat = handler.getValuesFromVariable("output-format").get(0);
    } else {
        outputFormat = "zip";
    }
    // Now tell your application to process the files whose paths are found in the
    // sourceFileList and write the resulting compressed file to the path held
    // in the outputFile String, using the format held in the outputFormat String.
}

That's it. The MainArgsHandler object handles the validation and data extraction of command-line arguments based on the flag and variable specification your code declares to it. Then your code just needs to use the foundFlag, foundVariable and getValuesFromVariable methods to check for the presence of flags and variables and get values from variables that were provided.

Note that you don't need to call foundVariable for the source or output-file command-line variables in the example above, because the specification has stated that both of these must occur at least once. If the user calls your application without providing each of these at least once then an IllegalArgumentException will cause the application to exit after processMainArgs is called. So, once you get past that, it's safe to assume that both of these variables must be found.

But the output-format command-line variable is optional so you ought to call foundVariable to determine whether or not it was provided by the user. You can simply call getValuesFromVariable and an empty List will be returned if the user did not supply a value for output-format, but remember that calling get(0) on an empty list will cause an IndexOutOfBoundsException to be thrown.

Notice that a Pattern has been specified in the declaration of the output-format command-line variable. This means that the value provided by the user for output-format must entirely (not partially) match the specified Pattern. In this case the pattern is a simple choice between "zip" or "tarball" but any valid Pattern object can be provided. If the command-line value for output-format does not entirely match the specified pattern then an IllegalArgumentException will be thrown.

Rules for command-line flags and variables

When using MainArgsHandler, command-line flags are supplied in the command-line call to your application by typing two hyphens followed by the flag name. A flag name must start and end with lowercase a-z characters, and single hyphens can be used within the name to effect spacing. So the name "example-flag-name" is valid, but "example--flag--name" is not valid (because double-hyphens are being used as spacing), nor is "example-name-" (because the name ends with a hyphen).

Command-line variables are supplied in the command-line call to your application by typing two hyphens, then the variable name, then an equals symbol, then the value the user wants to provide for that variable. A command-line variable name must follow the same rules which apply to a flag name. The value can be any sequence of printable characters, but bear in mind that the user will need to wrap the value in double-quote symbols if the value contains spaces, otherwise the space will cause the command-line to think that a new argument has been found. Also be aware that when double-quote symbols are used to wrap a value, these double-quote delimiters are stripped out before the value reaches the main method, so they do not form part of the value.

You cannot use the same name for both command-line flags and command-line variables.

Download MainArgsHandler

The source code for MainArgsHandler can be found in the uk.org.bobulous.java.startup repository on GitHub. Be aware that it also depends on the uk.org.bobulous.java.intervals package so you'll need to grab that package too.

If you find any issues with either of these packages, please raise them on GitHub rather than contact me directly.