Jay Taylor's notes

back to listing index

picocli - a mighty tiny command line interface

[web search]
Original source (picocli.info)
Tags: java command-line cli picocli picocli.info
Clipped on: 2023-03-03

picocli - a mighty tiny command line interface

version 4.7.1,  2023-01-27
Image (Asset 1/17) alt= @Option Field Type Default Arity Notes

boolean

0..1

Boolean options by default don’t require an option parameter. The field is set to the opposite of its default value when the option name is recognized. (This can be configured.)

Single-valued type (e.g., int, String, File)

1

The option name must be followed by a value.

Multi-valued type (arrays, collections or maps)

1

The option name must be followed by a value.

Prior to picocli 2.0, multi-valued options used to greedily consume as many arguments as possible until encountering another option or subcommand. If your application relies on the previous behaviour, you need to explicitly specify an option arity of 0..* when migrating to picocli 2.0.

§6.5.2. Positional Parameter Arity

Table 2. Default arity for @Parameters fields
@Parameters Field Type Default Arity Notes

boolean

1

Positional parameters of type boolean or Boolean require a value. Only true or false (case insensitive) are valid values.

Single-valued type (e.g., int, String, File)

1

One parameter required for each position.

Multi-valued type (arrays, collections or maps)

0..1

For multi-valued positional parameters (arrays, collections or maps), values are optional, not required.

@Parameters fields are applied to a command line argument if their index matches the argument’s position. The default index is *, meaning all positions. A @Parameters field with index = "*" is applied multiple times: once for each positional parameter on the command line.

When a @Parameters field is applied (because its index matches the index of the positional parameter), the field may consume zero, one or more arguments, depending on its arity.

§6.6. Optional Values

§6.6.1. Optional Option Parameters

When an option is defined with arity = "0..1", it may or not have a parameter value.

The fallback value determines what value is assigned when the option is specified without a value, while the default value determines what value is assigned when the option is not specified at all.

§6.6.2. Optional Parameter Use Cases

This feature is commonly used when an application wants to combine two options into one: the presence or absence of the option can be used like a boolean flag to trigger some behaviour, and the option value can be used to modify this behaviour.

An example use case is an option that switches on logging when present, with an optional value to set the log level. For example:

Java
Kotlin
java@Option(names = "--syslog", defaultValue = "OFF", fallbackValue = "INFO",
    description = {
        "When specified without arguments, start sending syslog messages at INFO level.",
        "If absent, no messages are sent to syslog.",
        "Optionally specify a severity value. Valid values: ${COMPLETION-CANDIDATES}."})
MyLogLevel syslogLevel;

Another example use case is password options.

§6.6.3. Optional Parameter Limitations

Be careful when defining commands that have both an option with an optional parameter and a positional parameter.

The picocli parser is "greedy" when it handles optional parameters: it looks at the value following the option name, and if that value is likely to be a parameter (not another option or subcommand) then it will process the value as a parameter for that option. This may not always be what you want.

For example:

Java
Kotlin
javaclass Ambiguous {
    @Parameters(description = "The file (required).")
    File file;

    @Option(names = "-x", arity = "0..1",
      description = "Option with optional parameter")
    String value;
}

When -x VALUE is specified on the command line, this results in an error: Missing required parameter: <file>.

Users can use the end-of-options delimiter and disambiguate the input with -x ‐‐ VALUE, but this may not be obvious to many users. One idea is to show the end-of-options delimiter in the usage help. Another idea is to make use of the IParameterPreprocessor Parser Plugin introduced with picocli 4.6.

An alternative is to avoid the use of optional parameters and use the default arity in this scenario to eliminate the ambiguity altogether.

§7. Required Arguments

§7.1. Required Options

Options can be marked required to make it mandatory for the user to specify them on the command line. When a required option is not specified, a MissingParameterException is thrown from the parse method. For example:

Java
Kotlin
javaclass MandatoryOption {
    @Option(names = "-n", required = true, description = "mandatory number")
    int number;

    @Parameters
    File[] files;
}

The following command line arguments would result in an exception complaining that number is missing:

// invalid: missing option -n <command> file1 file2 file3

The following command line arguments would be accepted:

// valid: required option -n has a value <command> -n 123 file1 file2 file3

§7.2. Required Parameters

Single-value @Parameters are always mandatory, because single-value positional parameters have arity = "1" by default.

The arity attribute can be used to make multi-value @Parameters mandatory:

Java
Kotlin
javaclass BothOptionAndParametersMandatory {
    @Parameters(arity = "1..*", description = "at least one File")
    File[] files;

    @Option(names = "-n", required = true, description = "mandatory number")
    int number;
}

The following command line arguments would result in an exception complaining that files are missing:

// invalid: missing file parameters <command> -n 123

The following command line arguments would be accepted:

// valid: both required fields have a value <command> -n 123 file1

§7.3. Options with an Optional Parameter

§8. Argument Groups

Picocli 4.0 introduces a new @ArgGroup annotation and its ArgGroupSpec programmatic equivalent.

Argument Groups can be used to define:

  • mutually exclusive options

  • options that must co-occur (dependent options)

  • option sections in the usage help message

  • repeating composite arguments

To create a group using the annotations API, annotate a field or method with @ArgGroup. The field’s type refers to the class containing the options and positional parameters in the group. (For annotated interface methods this would be the return type, for annotated setter methods in a concrete class this would be the setter’s parameter type.)

Picocli will instantiate this class when needed to capture command line argument values in the @Option and @Parameters-annotated fields and methods of this class.

Inherited Options currently cannot be used in Argument Groups. Applications that want to reuse Argument Groups across subcommands need to use Mixins. See this examplehttps://github.com/remkop/picocli/blob/main/picocli-examples/src/main/java/picocli/examples/arggroup/ArgGroupMixinDemo2.java for sharing an Argument Group defining global options between subcommands.

§8.1. Mutually Exclusive Options

Annotate a field or method with @ArgGroup(exclusive = true) to create a group of mutually exclusive options and positional parameters. For example:

Java
Kotlin
java@Command(name = "exclusivedemo")
public class MutuallyExclusiveOptionsDemo {

    @ArgGroup(exclusive = true, multiplicity = "1")
    Exclusive exclusive;

    static class Exclusive {
        @Option(names = "-a", required = true) int a;
        @Option(names = "-b", required = true) int b;
        @Option(names = "-c", required = true) int c;
    }
}

The above example defines a command with mutually exclusive options -a, -b and -c.

The group itself has a multiplicity attribute that defines how many times the group may be specified within the command. The default is multiplicity = "0..1", meaning that by default a group may be omitted or specified once. In this example the group has multiplicity = "1", so the group must occur once: one of the exclusive options must occur on the command line.

The synopsis of this command is:

Usage: exclusivedemo (-a=<a> | -b=<b> | -c=<c>)

When one of the options in the group is matched, picocli creates an instance of the Exclusive class and assigns it to the @ArgGroup-annotated exclusive field.

Note that the options are defined as required = true; this means required within the group, not required within the command.

As of picocli 4.1.2, all options in an exclusive group are automatically considered required, even if they are not marked as required = true in the annotations. Applications using older versions of picocli should mark all options in exclusive groups as required.

Picocli will validate the arguments and throw a MutuallyExclusiveArgsException if multiple mutually exclusive arguments were specified. For example:

Java
Kotlin
javaMutuallyExclusiveOptionsDemo example = new MutuallyExclusiveOptionsDemo();
CommandLine cmd = new CommandLine(example);

try {
    cmd.parseArgs("-a=1", "-b=2");
} catch (MutuallyExclusiveArgsException ex) {
    assert "Error: -a=<a>, -b=<b> are mutually exclusive (specify only one)"
            .equals(ex.getMessage());
}

For the above group, only one of the options can be specified. Any other combination of options, or the absence of options, is invalid.

Picocli will not initialize the @ArgGroup-annotated field if none of the group options is specified on the command line. For optional groups (groups with multiplicity = "0..1" - the default) this means that the @ArgGroup-annotated field may remain null.

§8.2. Mutually Dependent Options

§8.2.1. Overview

Annotate a field or method with @ArgGroup(exclusive = false) to create a group of dependent options and positional parameters that must co-occur. For example:

Java
Kotlin
java@Command(name = "co-occur")
public class DependentOptionsDemo {

    @ArgGroup(exclusive = false)
    Dependent dependent;

    static class Dependent {
        @Option(names = "-a", required = true) int a;
        @Option(names = "-b", required = true) int b;
        @Option(names = "-c", required = true) int c;
    }
}

The above example defines a command with dependent options -a, -b and -c that must co-occur.

The group itself has a multiplicity attribute that defines how many times the group may be specified within the command. In this example the group uses the default multiplicity, multiplicity = "0..1", meaning that the group may be omitted or specified once.

The synopsis of this command is:

Usage: co-occur [-a=<a> -b=<b> -c=<c>]

When the first option in the group is matched, picocli creates an instance of the Dependent class and assigns it to the @ArgGroup-annotated dependent field.

Note that the options are defined as required = true; this means required within the group, not required within the command.

Picocli will validate the arguments and throw a MissingParameterException if not all dependent arguments were specified. For example:

Java
Kotlin
javaDependentOptionsDemo example = new DependentOptionsDemo();
CommandLine cmd = new CommandLine(example);

try {
    cmd.parseArgs("-a=1", "-b=2");
} catch (MissingParameterException ex) {
    assert "Error: Missing required argument(s): -c=<c>".equals(ex.getMessage());
}
Picocli will not initialize the @ArgGroup-annotated field if none of the group options is specified on the command line. For optional groups (groups with multiplicity = "0..1" - the default) this means that the @ArgGroup-annotated field may remain null.

§8.2.2. Non-Required Options in Mutually Dependent Groups

In mutually dependent groups it is possible to have one or more options that are not required. This is different from exclusive groups, where all options are always required.

It is useful to be able to define a co-occurring group as (-a -b [-c]) so that both -a -b -c and -a -b are valid on the command line, but not -a -c for example. This can be implemented by marking the optional option with required = false, as in the below example:

Java
Kotlin
java@Command(name = "co-occur-with-optional-options")
public class DependentWithOptionalOptionsDemo {

    @ArgGroup(exclusive = false, multiplicity = "1")
    DependentWithOptionalOptions group;

    static class DependentWithOptionalOptions {
        @Option(names = "-a", required = true)  int a;
        @Option(names = "-b", required = true)  int b;
        @Option(names = "-c", required = false) int c;
    }
}

More than one option can be optional in mutually dependent groups, but it is recommended to have at least one required option in the group (or there is not much point in using a mutually dependent group).

§8.3. Option Sections in Usage Help

§8.3.1. Use Heading to Enable Option Sections

The example below uses groups to define options sections in the usage help. When a group has a non-null heading (or headingKey), the options in the group are given the specified heading in the usage help message. The headingKey attribute can be used to get the heading text from the command’s resource bundle.

This works for mutually exclusive or co-occurring groups, but it is also possible to define a group that does no validation but only creates an option section in the usage help.

Annotate a field or method with @ArgGroup(validate = false) to create a group for display purposes only. For example:

Java
Kotlin
java@Command(name = "sectiondemo", description = "Section demo")
public class OptionSectionDemo {

    @ArgGroup(validate = false, heading = "This is the first section%n")
    Section1 section1;

    static class Section1 {
        @Option(names = "-a", description = "Option A") int a;
        @Option(names = "-b", description = "Option B") int b;
        @Option(names = "-c", description = "Option C") int c;
    }

    @ArgGroup(validate = false, heading = "This is the second section%n")
    Section2 section2;

    static class Section2 {
        @Option(names = "-x", description = "Option X") int x;
        @Option(names = "-y", description = "Option Y") int y;
        @Option(names = "-z", description = "Option Z") int z;
    }

    public static void main(String[] args) {
        new CommandLine(new OptionSectionDemo()).usage(System.out);
    }
}

This prints the following usage help message:

Usage: sectiondemo [-a=<a>] [-b=<b>] [-c=<c>] [-x=<x>] [-y=<y>] [-z=<z>] Section demo This is the first section -a=<a> Option A -b=<b> Option B -c=<c> Option C This is the second section -x=<x> Option X -y=<y> Option Y -z=<z> Option Z

Note that the heading text must end with %n to insert a newline between the heading text and the first option. This is for consistency with other headings in the usage help, like @Command(headerHeading = "Usage:%n", optionListHeading = "%nOptions:%n").

Picocli will not initialize the @ArgGroup-annotated field if none of the group options is specified on the command line. For optional groups (groups with multiplicity = "0..1" - the default) this means that the @ArgGroup-annotated field may remain null.

§8.3.2. Option Section Order

Options that are not in any argument group are always displayed before any group option sections.

The ordering of group option sections can be controlled with the order attribute. For example:

Java
Kotlin
java@ArgGroup(heading = "First%n", order = 1) Section1 section1;
@ArgGroup(heading = "Next%n",  order = 2) Section2 section2;
@ArgGroup(heading = "Last%n",  order = 3) Section3 section3;

§8.3.3. Validation Trade-offs

Note that setting validate = false means that picocli won’t validate user input for the group. For example, even for groups with multiplicity = 1, when the end user specifies the group multiple times, no error is shown. If the group is a single-value field, only the last occurrence is stored and previous occurrences are silently dropped.

If validation is needed, the recommendation is to make the field holding the group a collection, and doing Custom Validation. For example to ensure that this collection holds only a single element:

Java
java@ArgGroup(validate = false, heading = "This is the first section%n", multiplicity = "0..1")
private List<Section1> section1List = new ArrayList<>();

@Spec CommandSpec spec;

// validate in the business logic
public void run() {
    if (section1List.size() > 1) {
        throw new ParameterException(spec.commandLine(),
                "Group [-a=<a>] [-b=<b>] [-c=<c>] can be specified at most once.");
    }
    // remaining business logic...
}

§8.4. Repeating Composite Argument Groups

The below example shows how groups can be composed of other groups, and how arrays and collections can be used to capture repeating groups (with a multiplicity greater than one):

Java
Kotlin
java@Command(name = "repeating-composite-demo")
public class CompositeGroupDemo {

    @ArgGroup(exclusive = false, multiplicity = "1..*")
    List<Composite> composites;

    static class Composite {
        @ArgGroup(exclusive = false, multiplicity = "0..1")
        Dependent dependent;

        @ArgGroup(exclusive = true, multiplicity = "1")
        Exclusive exclusive;
    }

    static class Dependent {
        @Option(names = "-a", required = true) int a;
        @Option(names = "-b", required = true) int b;
        @Option(names = "-c", required = true) int c;
    }

    static class Exclusive {
        @Option(names = "-x", required = true) boolean x;
        @Option(names = "-y", required = true) boolean y;
        @Option(names = "-z", required = true) boolean z;
    }
}

In the above example, the annotated composites field defines a composite group that must be specified at least once, and may be specified many times (multiplicity = "1..*"), on the command line. Notice that for multi-value groups the type of the @ArgGroup-annotated field must be a collection or an array to capture the multiple Composite instances that hold the values that were matched on the command line.

The synopsis of this command is:

Usage: repeating-composite-demo ([-a=<a> -b=<b> -c=<c>] (-x | -y | -z))...

Each time the group is matched, picocli creates an instance of the Composite class and adds it to the composites list.

The Composite class itself contains two groups: an optional (multiplicity = "0..1") group of dependent options that must co-occur, and another group of mutually exclusive options, which is mandatory (multiplicity = "1").

The below example illustrates:

Java
Kotlin
javaCompositeGroupDemo example = new CompositeGroupDemo();
CommandLine cmd = new CommandLine(example);

cmd.parseArgs("-x", "-a=1", "-b=1", "-c=1", "-a=2", "-b=2", "-c=2", "-y");
assert example.composites.size() == 2;

Composite c1 = example.composites.get(0);
assert c1.exclusive.x;
assert c1.dependent.a == 1;
assert c1.dependent.b == 1;
assert c1.dependent.c == 1;

Composite c2 = example.composites.get(1);
assert c2.exclusive.y;
assert c2.dependent.a == 2;
assert c2.dependent.b == 2;
assert c2.dependent.c == 2;

Picocli will not initialize the @ArgGroup-annotated field if none of the group options is specified on the command line. For optional groups (groups with multiplicity = "0..1" - the default) this means that the @ArgGroup-annotated field may remain null. If the application assigned a non-null Collection in the field declaration (e.g., @ArgGroup List<Composite> composites = new ArrayList<>();), then the collection will remain empty if none of the group options is specified on the command line.

§8.5. Default Values in Argument Groups

The default values of options in an argument group are applied when at least one option in the group is matched on the command line and picocli instantiates the user object of the group.

Picocli will not initialize the @ArgGroup-annotated field (and so no default values are applied) if none of the group options is specified on the command line.

§8.5.1. Showing Default Values in Group Usage Help

Options used in argument groups should define default values via the @Option(defaultValue = "…​") annotation.

When default values are defined in the annotation, the ${DEFAULT-VALUE} variable can be used to show the default value in the description of options in an argument group. For example:

Java
Kotlin
javaclass GoodGroup {
    @Option(names = "-x", defaultValue = "123", description = "Default: ${DEFAULT-VALUE}")
    int x;
}

@Command(name = "good", description = "usage help shows the default value")
class GoodExample {
    @ArgGroup GoodGroup goodGroup;

    public static void main(String[] args) {
        new CommandLine(new GoodExample()).usage(System.out);
    }
}

When the default value is defined in the annotation, the usage help shows the correct default value:

Usage: good [[-x=<x>]] usage help shows the default value -x=<x> Default: 123

Picocli will not be able to retrieve the default values that are defined by assigning a value in the declaration of an @Option-annotated field in a group. For example:

Java
Kotlin
javaclass BadGroup {
    @Option(names = "-x", description = "Default: ${DEFAULT-VALUE}")
    int x = 123; // value not found until `BadGroup` is instantiated
}

@Command(name = "bad", description = "usage help shows the wrong default value")
class BadExample {
    @ArgGroup BadGroup badGroup;

    public static void main(String[] args) {
        new CommandLine(new BadExample()).usage(System.out);
    }
}

When the default value is defined in the field declaration and not in the annotation, usage help for the options in the group incorrectly shows null as the default value:

Usage: bad [[-x=<x>]] usage help shows the wrong default value -x=<x> Default: null

§8.5.2. Assigning Default Values in Argument Groups

Applications need to do extra work for argument group options with default values. Picocli does not instantiate the group if none of the options in the group is specified on the command line, so applications need to do this manually.

Below are some recommendations for using default values in argument group options and positional parameters:

  • specify default values in both the @Option annotation, and in the initial value of the @Option-annotated field. Yes, that means some duplication. This recommendation also holds for positional @Parameters.

  • the application needs to manually instantiate the @ArgGroup-annotated field. More details follow below.

The default value in the @Option or @Parameters annotation means that picocli can show the default value in the usage help, and the initial value means that any new instance of the group that contains the option will already have the default value assigned to that option field.

The example below shows an option that defines the default value in the annotation as well as in the initial value of the field:

Java
Kotlin
javaclass MyGroup {
    // (group options):
    // specify default values both in the annotation and in the initial value
    @Option(names = "-x", defaultValue = "XX")
    String x = "XX"; // yes, some duplication :-(
}

Next, the application needs to manually instantiate the @ArgGroup-annotated field. There is a trade-off:

  • instantiating the @ArgGroup-annotated field in the declaration is simple and short but applications cannot easily detect whether a group option was specified on the command line or not

  • leaving the @ArgGroup-annotated field null in the declaration allows applications to easily detect whether a group option was specified on the command line, but is a bit more code

The example below shows the first idea: instantiating the group object in the declaration. This way, the group object is never null and (if you followed the previous recommendation) all option fields in this group object will have the default value as their initial value.

Java
Kotlin
java// instantiating the group in the declaration:
// all options in this group now also have their initial (default) value
@ArgGroup MyGroup myGroup = new MyGroup();

Alternatively, applications can initialize the group objects in the business logic: in the run or call method. This allows the application to determine whether the user specified a value for any of the options in the group.

The example below demonstrates initializing the group objects in the business logic:

Java
Kotlin
java@Command(name = "group-default-demo")
class MyApp implements Runnable {
    @ArgGroup Outer outer; // no initial value

    static class Outer {
        @Option(names = "-x", defaultValue = "XX") String x = "XX";

        @ArgGroup(exclusive = true)
        Inner inner; // no initial value
    }

    static class Inner {
        @Option(names = "-a", defaultValue = "AA") String a = "AA";
        @Option(names = "-b", defaultValue = "BB") String b = "BB";
    }

    public void run() {
        if (outer == null) { // -x option was not specified on command line
            // perform any logic that needs to happen if -x is missing
            outer = new Outer(); // assign default values
        }
        if (outer.inner == null) { // neither -a nor -b was specified
            // perform any logic that needs to happen if -a or -b is missing
            outer.inner = new Inner(); // assign defaults for inner group
        }

        // remaining business logic...
    }
}

§8.6. Positional Parameters

When a @Parameters positional parameter is part of a group, its index is the index within the group, not within the command.

Below is an example of an application that uses a repeating group of positional parameters:

Java
Kotlin
java@Command(name = "grades", mixinStandardHelpOptions = true, version = "grades 1.0")
public class Grades implements Runnable {

    static class StudentGrade {
        @Parameters(index = "0") String name;
        @Parameters(index = "1") BigDecimal grade;
    }

    @ArgGroup(exclusive = false, multiplicity = "1..*")
    List<StudentGrade> gradeList;

    @Override
    public void run() {
        gradeList.forEach(e -> System.out.println(e.name + ": " + e.grade));
    }

    public static void main(String[] args) {
        System.exit(new CommandLine(new Grades()).execute(args));
    }
}

Running the above program with this input:

Alice 3.1 Betty 4.0 "X Æ A-12" 3.5 Zaphod 3.4

Produces the following output:

Alice: 3.1 Betty: 4.0 X Æ A-12: 3.5 Zaphod: 3.4

§8.7. Argument Group Limitations

  • Options with the same name cannot be defined in multiple groups. Similarly, it is not possible to define an option outside of a group with the same name as a different option that is part of a group.

  • Positional parameters in a single group work fine, but take care (or avoid) defining positional parameters in multiple groups or positional parameters in a group as well as outside a group. Positional parameters are matched by index, and while the index of a group is reset when a new group multiple is encountered, the index of positional parameters outside a group only increases and is never reset.

  • Some relationships between options cannot be expressed with picocli argument groups. In general, picocli argument groups can only express relationship for which you can write a command line synopsis with every option occurring only once. For example, it is not possible to use argument groups to create a relationship with exclusive options [-a | -b], where -a requires another option -c: [[-a] -c], while at the same time -b is independent of -c: [-b] [-c]. The application may need to do some programmatic validation in such cases.

§9. Executing Commands

Parsing the command line arguments is the first step. A robust real-world application needs to handle a number of scenarios:

  1. User input was invalid

  2. User requested usage help (potentially for a subcommand)

  3. User requested version help (potentially for a subcommand)

  4. None of the above: we can run the business logic (potentially for a subcommand)

  5. The business logic may throw an exception

Picocli 4.0 introduces an execute method for handling all of the above scenarios in a single line of code. For example:

Java
Kotlin
javanew CommandLine(new MyApp()).execute(args);

With the execute method, application code can be extremely compact:

Java
Kotlin
java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Command(name = "myapp", mixinStandardHelpOptions = true, version = "1.0")
class MyApp implements Callable<Integer> {

    @Option(names = "-x") int x;

    @Override
    public Integer call() { // business logic
        System.out.printf("x=%s%n", x);
        return 123; // exit code
    }

    public static void main(String... args) { // bootstrap the application
        System.exit(new CommandLine(new MyApp()).execute(args));
    }
}

Despite being only 15 lines long, this is a full-fledged application, with --help and --version options in addition to the -x option. The execute method will show the usage help or version information if requested by the user, and invalid user input will result in a helpful error message. If the user input was valid, the business logic is invoked. Finally, the execute method returns an exit status code that can be used to call System.exit if desired.

A command is executable if its user object implements Runnable or Callable, or is a @Command-annotated Method. Examples follow below.
The execute method replaces the older run, call, invoke and parseWithHandlers methods.

The DIY Command Execution section shows an example of the boilerplate code that can be omitted with the execute method.

§9.1. Exit Code

Many command line applications return an exit codehttps://en.wikipedia.org/wiki/Exit_status to signify success or failure. Zero often means success, a non-zero exit code is often used for errors, but other than that, meanings differ per application.

The CommandLine.execute method introduced in picocli 4.0 returns an int, and applications can use this return value to call System.exit if desired. For example:

Java
Kotlin
javapublic static void main(String... args) {
  int exitCode = new CommandLine(new MyApp()).execute(args);
  System.exit(exitCode);
}
Older versions of picocli had some limited exit code support where picocli would call System.exit, but this is now deprecated.

§9.2. Generating an Exit Code

@Command-annotated classes that implement Callable and @Command-annotated methods can simply return an int or Integer, and this value will be returned from CommandLine.execute. For example:

Java
Kotlin
java@Command(name = "greet")
class Greet implements Callable<Integer> {
    public Integer call() {
        System.out.println("hi");
        return 1;
    }

    // define a "shout" subcommand with a @Command-annotated method
    @Command
    int shout() {
        System.out.println("HI!");
        return 2;
    }
}

assert 1 == new CommandLine(new Greet()).execute();
assert 2 == new CommandLine(new Greet()).execute("shout");

Commands with a user object that implements Runnable can implement the IExitCodeGenerator interface to generate an exit code. For example:

Java
Kotlin
java@Command(name = "wave")
class Gesture implements Runnable, IExitCodeGenerator {

    @Override public void run() {
        System.out.println("wave");
    }

    @Override public int getExitCode() {
        return 3;
    }
}

assert 3 == new CommandLine(new Gesture()).execute();

§9.3. Exception Exit Codes

By default, the execute method returns CommandLine.ExitCode.OK (0) on success, CommandLine.ExitCode.SOFTWARE (1) when an exception occurred in the Runnable, Callable or command method, and CommandLine.ExitCode.USAGE (2) for invalid input. (These are common values according to this StackOverflow answerhttps://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux/40484670#40484670). This can be customized with the @Command annotation. For example:

java@Command(exitCodeOnInvalidInput = 123,
   exitCodeOnExecutionException = 456)

Additionally, applications can configure a IExitCodeExceptionMapper to map a specific exception to an exit code:

Java
Kotlin
javaclass MyMapper implements IExitCodeExceptionMapper {
    @Override
    public int getExitCode(Throwable t) {
        if (t instanceof FileNotFoundException) {
            return 74;
        }
        return 1;
    }
}

When the end user specified invalid input, the execute method prints an error message followed by the usage help message of the command, and returns an exit code. This can be customized by configuring an IParameterExceptionHandler.

If the business logic of the command throws an exception, the execute method prints the stack trace of the exception and returns an exit code. This can be customized by configuring an IExecutionExceptionHandler.

§9.4. Usage Help Exit Code Section

By default, the usage help message does not include exit code information. Applications that call System.exit need to configure the usage help message to show exit code details, either with the exitCodeListHeading and exitCodeList annotation attributes, or programmatically by calling UsageMessageSpec.exitCodeListHeading and UsageMessageSpec.exitCodeList.

See Exit Code List for details.

§9.5. Execution Configuration

The following methods can be used to configure the behaviour of the execute method:

The above methods are not applicable with (and ignored by) other entry points like parse, parseArgs, populateCommand, run, call, invoke, parseWithHandler and parseWithHandlers.

§9.6. Migration

Older versions of picocli supported run, call, invoke and parseWithHandlers convenience methods that were similar to execute but had limited support for parser configuration and limited support for exit codes. These methods are deprecated from picocli 4.0. The sections below show some common usages and how the same can be achieved with the execute API.

§9.6.1. Customizing Output Streams and ANSI settings

Before:

Java
Kotlin
javaPrintStream out = // output stream for user-requested help
PrintStream err = // output stream for error messages
Ansi ansi = // to use ANSI colors and styles or not
CommandLine.run(new MyRunnable(), out, err, ansi, args);

After:

Java
Kotlin
javaPrintWriter out = // output stream for user-requested help
PrintWriter err = // output stream for error messages
Ansi ansi = // to use ANSI colors and styles or not

CommandLine cmd = new CommandLine(new MyRunnable())
        .setOut(out);
        .setErr(err);
        .setColorScheme(Help.defaultColorScheme(ansi));

int exitCode = cmd.execute(args);

§9.6.2. Return Value from Callable or Method

Before:

Java
Kotlin
javaclass MyCallable implements Callable<MyResult> {
    public MyResult call() { /* ... */ }
}

MyResult result = CommandLine.call(new MyCallable(), args);

After:

Java
Kotlin
javaCommandLine cmd = new CommandLine(new MyCallable());
int exitCode = cmd.execute(args);
MyResult result = cmd.getExecutionResult();

§9.6.3. Invoking Command Methods

Before:

Java
Kotlin
javaclass MyCommand {
    @Command
    public MyResult doit(@Option(names = "-x") int x) { ... }
}

MyResult result = CommandLine.invoke("doit", MyCommand.class, args);

After:

Java
Kotlin
javaMethod doit = CommandLine.getCommandMethods(MyCommand.class, "doit").get(0);
CommandLine cmd = new CommandLine(doit);
int exitCode = cmd.execute(args);
MyResult result = cmd.getExecutionResult();

§9.6.4. Executing Commands with Subcommands

The IParseResultHandler2 interface has been deprecated in picocli 4.0 in favor of IExecutionStrategy. The existing built-in handlers RunLast, RunAll and RunFirst implement the IExecutionStrategy interface and can still be used:

  • the RunLast handler prints help if requested, and otherwise gets the last specified command or subcommand and tries to execute it as a Runnable, Callable or Method. This is the default execution strategy.

  • the RunFirst handler prints help if requested, and otherwise executes the top-level command as a Runnable, Callable or Method

  • the RunAll handler prints help if requested, and otherwise executes all commands and subcommands that the user specified on the command line as Runnable, Callable or Method tasks

Before

Java
Kotlin
javaCommandLine cmd = new CommandLine(MyTopLevelCommand())
        .addSubcommand("status",   new GitStatus())
        .addSubcommand("commit",   new GitCommit())
        .addSubcommand("add",      new GitAdd());
List<Object> result = cmd.parseWithHandler(new RunAll(), args);

After

Java
Kotlin
javaCommandLine cmd = new CommandLine(MyTopLevelCommand())
        .addSubcommand("status",   new GitStatus())
        .addSubcommand("commit",   new GitCommit())
        .addSubcommand("add",      new GitAdd());

// the default is RunLast, this can be customized:
cmd.setExecutionStrategy(new RunAll());
int exitCode = cmd.execute(args);

The ParseResult can be used to get the return value from a Callable or Method subcommand:

Java
Kotlin
java// getting return value from Callable or Method command
Object topResult = cmd.getExecutionResult();

// getting return value from Callable or Method subcommand
ParseResult parseResult = cmd.getParseResult();
if (parseResult.subcommand() != null) {
    CommandLine sub = parseResult.subcommand().commandSpec().commandLine();
    Object subResult = sub.getExecutionResult();
}

§9.7. DIY Command Execution

Alternatively, applications may want to use the parseArgs method directly and write their own "Do It Yourself" command execution logic.

The example below covers the following common scenarios:

  1. Handle invalid user input, and report any problems to the user (potentially suggesting alternative options and subcommands for simple typos if we want to get fancy).

  2. Check if the user requested usage help, and print this help and abort processing if this was the case.

  3. Check if the user requested version information, and print this information and abort processing if this was the case.

  4. If none of the above, run the business logic of the application.

  5. Handle any errors that occurred in the business logic.

Java
Kotlin
javaCallable<Object> callable = new MyCallable();
CommandLine cmd = new CommandLine(callable);
try {
    ParseResult parseResult = cmd.parseArgs(args);

    // Did user request usage help (--help)?
    if (cmd.isUsageHelpRequested()) {
        cmd.usage(cmd.getOut());
        return cmd.getCommandSpec().exitCodeOnUsageHelp();

    // Did user request version help (--version)?
    } else if (cmd.isVersionHelpRequested()) {
        cmd.printVersionHelp(cmd.getOut());
        return cmd.getCommandSpec().exitCodeOnVersionHelp();
    }
    // invoke the business logic
    Object result = callable.call();
    cmd.setExecutionResult(result);
    return cmd.getCommandSpec().exitCodeOnSuccess();

// invalid user input: print error message and usage help
} catch (ParameterException ex) {
    cmd.getErr().println(ex.getMessage());
    if (!UnmatchedArgumentException.printSuggestions(ex, cmd.getErr())) {
        ex.getCommandLine().usage(cmd.getErr());
    }
    return cmd.getCommandSpec().exitCodeOnInvalidInput();

// exception occurred in business logic
} catch (Exception ex) {
    ex.printStackTrace(cmd.getErr());
    return cmd.getCommandSpec().exitCodeOnExecutionException();
}

The CommandLine.execute method is equivalent to the above, and additionally handles subcommands correctly.

§9.8. Handling Errors

Internally, the execute method parses the specified user input and populates the options and positional parameters defined by the annotations. When the user specified invalid input, this is handled by the IParameterExceptionHandler.

After parsing the user input, the business logic of the command is invoked: the run, call or @Command-annotated method. When an exception is thrown by the business logic, this is handled by the IExecutionExceptionHandler.

In most cases, the default handlers are sufficient, but the sections below show how they can be customized.

§9.8.1. Invalid User Input

When the user specified invalid input, the parser throws a ParameterException. In the execute method, such exceptions are caught and passed to the IParameterExceptionHandler.

The default parameter exception handler prints an error message describing the problem, followed by either suggested alternativeshttps://picocli.info/apidocs-all/info.picocli/picocli/CommandLine.UnmatchedArgumentException.html#printSuggestions(picocli.CommandLine.ParameterException,java.io.PrintWriter) for mistyped options, or the full usage help message of the problematic command. Finally, the handler returns an exit code. This is sufficient for most applications.

Sometimes you want to display a shorter message. For example, the grep utility does not show the full usage help when it gets an invalid argument:

$ grep -d recurese "ERROR" logs/* Error: invalid argument ‘recurese’ for ‘--directories’ Valid arguments are: - ‘read’ - ‘recurse’ - ‘skip’ Usage: grep [OPTION]... PATTERN [FILE]... Try 'grep --help' for more information.

You can customize how your application handles invalid user input by setting a custom IParameterExceptionHandler:

Java
Kotlin
javanew CommandLine(new MyApp())
    .setParameterExceptionHandler(new ShortErrorMessageHandler())
    .execute(args);

Where the IParameterExceptionHandler implementation could be something like this:

Java
Kotlin
javaclass ShortErrorMessageHandler implements IParameterExceptionHandler {

    public int handleParseException(ParameterException ex, String[] args) {
        CommandLine cmd = ex.getCommandLine();
        PrintWriter err = cmd.getErr();

        // if tracing at DEBUG level, show the location of the issue
        if ("DEBUG".equalsIgnoreCase(System.getProperty("picocli.trace"))) {
            err.println(cmd.getColorScheme().stackTraceText(ex));
        }

        err.println(cmd.getColorScheme().errorText(ex.getMessage())); // bold red
        UnmatchedArgumentException.printSuggestions(ex, err);
        err.print(cmd.getHelp().fullSynopsis());

        CommandSpec spec = cmd.getCommandSpec();
        err.printf("Try '%s --help' for more information.%n", spec.qualifiedName());

        return cmd.getExitCodeExceptionMapper() != null
                    ? cmd.getExitCodeExceptionMapper().getExitCode(ex)
                    : spec.exitCodeOnInvalidInput();
    }
}

§9.8.2. Business Logic Exceptions

When the business logic throws an exception, this exception is caught and passed to the IExecutionExceptionHandler.

The default execution exception handling results in the stack trace of the exception being printed and an exit code being returned. This is sufficient for most applications.

If you have designed your business logic to throw exceptions with user-facing error messages, you want to print this error message instead of the stack trace. This can be accomplished by installing a custom IExecutionExceptionHandler, like this:

Java
Kotlin
javanew CommandLine(new MyApp())
    .setExecutionExceptionHandler(new PrintExceptionMessageHandler())
    .execute(args);

Where the IExecutionExceptionHandler implementation could look something like this:

Java
Kotlin
javaclass PrintExceptionMessageHandler implements IExecutionExceptionHandler {
    public int handleExecutionException(Exception ex,
                                        CommandLine cmd,
                                        ParseResult parseResult) {

        // bold red error message
        cmd.getErr().println(cmd.getColorScheme().errorText(ex.getMessage()));

        return cmd.getExitCodeExceptionMapper() != null
                    ? cmd.getExitCodeExceptionMapper().getExitCode(ex)
                    : cmd.getCommandSpec().exitCodeOnExecutionException();
    }
}

§9.9. Rare Use Cases

The CommandLine::execute method is the recommended way to execute your command line application, as it provides configurable exception handling, handles user requests for usage help or version information, results in short and simple application code, and never throws an exception.

However, there may be use cases for which the execute method is not a good match. The alternative is to use CommandLine::parseArgs and handle the resulting ParseResult object in your application. The DIY Command Execution section shows what is involved in doing so.

The parseArgs method may be useful when writing parser test code, or when your application’s main method is called by another application. The following sections go into some detail.

§9.9.1. Parser Test Code Example

The parseArgs method is useful in test code that only exercises the parsing logic, without involving the business logic. For example:

Java
Groovy
javaMyApp app = new MyApp();
new CommandLine(app).parseArgs("--some --options and parameters".split(" "));
assertTrue(app.some);

§9.9.2. Percolating Exceptions Up

The execute method never throws an exception, and for some applications this is undesirable.

The parseArgs method can also be useful when the main method of your application is called by another application, and this other application is responsible for error handling.

The Maven exec:java goal invokes the target class in the same Maven process. In this case, we don’t want to call System.exit, because it would stop the entire Maven process, and additionally, we want the exceptions thrown by the command line application to be handled by Maven.

One idea is to provide a separate main method that uses parseArgs instead of execute. For example:

Java
javapublic class MyApp implements Callable {
    /** Calls System.exit when called from the command line. */
    public static void main(String... args) throws Exception {
        System.exit(new CommandLine(new MyApp()).execute(args));
    }

    /**
     * Nested helper class that can be used safely from the build tool:
     * it does not call System.exit and it percolates exceptions up
     * for handling by the caller.
     */
    public static class NoSystemExit {
        public static void main(String... args) throws Exception {
            MyApp app = new MyApp();
            ParseResult pr = new CommandLine(app).parseArgs(args);
            if (CommandLine.executeHelpRequest(pr) != null) { return; } // help was requested
            app.call(); // execute business logic, which may also throw an exception
        }
    }
    //...

Then, in the build configuration, invoke nested class MyApp.NoSystemExit instead of MyApp to let the build tool handle any exceptions and avoid calling System.exit.

§9.9.3. System.exit or not?

An alternative to the above solution is to decide at runtime whether to call System.exit or not.

The example implementation below demonstrates how to use system properties to determine whether to call System.exit or not:

Java
javapublic static void main(String... args) {
    int exitCode = new CommandLine(new App()).execute(args);
    if ((exitCode == CommandLine.ExitCode.OK && exitOnSuccess())
    || (exitCode != CommandLine.ExitCode.OK && exitOnError())) {
        System.exit(exitCode);
    }
}

private static boolean exitOnSuccess() {
    return syspropDefinedAndNotFalse("systemExitOnSuccess");
}

private static boolean exitOnError() {
    return syspropDefinedAndNotFalse("systemExitOnError");
}

private static boolean syspropDefinedAndNotFalse(String key) {
    String value = System.getProperty(key);
    return value != null && !"false".equalsIgnoreCase(value);
}

Picocli’s own Bash and ZSH completion script generator tool uses this method: when this tool is called with a specific system property (picocli.autocomplete.systemExitOnError) it will call System.exit when an error occurs.

§10. Validation

§10.1. Built-in Validation

Picocli provides some limited form of validation:

§10.2. Custom Validation

Most applications will need to do additional validations to verify some business rules.

Applications that use the execute API may find it useful to throw a ParameterException when validation fails: any ParameterExceptions will be caught and handled by picocli’s built-in error handler, which shows the error message in bold red, and is followed by the usage help message.

To construct a ParameterException, you need the CommandLine instance where the error occurred. This can be obtained from the CommandSpec, which in turn can be obtained from a @Spec-annotated field. The sections below show some examples.

§10.2.1. Single Value Validation

Methods annotated with @Option and @Parameters can do simple input validation by throwing a ParameterException when invalid values are specified on the command line.

The following example validates that the value specified for the --prime option is a prime number:

Java
Kotlin
javaclass SingleOptionValidationExample {
    private int prime;

    @Spec CommandSpec spec; // injected by picocli

    @Option(names = {"-p", "--prime"}, paramLabel = "NUMBER")
    public void setPrimeNumber(int value) {
        boolean invalid = false;
        for (int i = 2; i <= value / 2; i++) {
            if (value % i == 0) {
                invalid = true;
                break;
            }
        }
        if (invalid) {
            throw new ParameterException(spec.commandLine(),
                    String.format("Invalid value '%s' for option '--prime': " +
                            "value is not a prime number.", value));
        }
        prime = value;
    }
    // ...
}

§10.2.2. Validating Option Combinations

Another common scenario is that the combination of multiple options and positional parameters is valid. One way to accomplish this is to perform such validation at the beginning of the business logic.

The following example validates that at least one of the --xml, --csv, or --json options is specified:

Java
Kotlin
java@Command(name = "myapp", mixinStandardHelpOptions = true, version = "myapp 0.1")
class MultiOptionValidationExample implements Runnable {
    @Option(names="--xml")  List<File> xmlFiles;
    @Option(names="--csv")  List<File> csvFiles;
    @Option(names="--json") List<File> jsonFiles;

    @Spec CommandSpec spec; // injected by picocli

    public static void main(String... args) {
        System.exit(new CommandLine(new MultiOptionValidationExample()).execute(args));
    }

    public void run() {
        validate();

        // remaining business logic here
    }

    private void validate() {
        if (missing(xmlFiles) && missing(csvFiles) && missing(jsonFiles)) {
            throw new ParameterException(spec.commandLine(),
                    "Missing option: at least one of the " +
                    "'--xml', '--csv', or '--json' options must be specified.");
        }
    }

    private boolean missing(List<?> list) {
        return list == null || list.isEmpty();
    }
}

§10.2.3. JSR-380 BeanValidation

If you want to keep your validation declarative and annotation-based, take a look at JSR 380https://jcp.org/en/jsr/detail?id=380.

JSR-380 is a specification of the Java API for bean validation, part of JavaEE and JavaSE, which ensures that the properties of a bean meet specific criteria, using annotations such as @NotNull, @Min, and @Max.

Java
Kotlin
javaimport picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.*;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.constraints.*;
import java.util.Set;

// Example inspired by https://www.baeldung.com/javax-validation
public class User implements Runnable {

    @NotNull(message = "Name cannot be null")
    @Option(names = {"-n", "--name"}, description = "mandatory")
    private String name;

    @Min(value = 18, message = "Age should not be less than 18")
    @Max(value = 150, message = "Age should not be greater than 150")
    @Option(names = {"-a", "--age"}, description = "between 18-150")
    private int age;

    @Email(message = "Email should be valid")
    @Option(names = {"-e", "--email"}, description = "valid email")
    private String email;

    @Spec CommandSpec spec;

    public User() { }

    @Override
    public String toString() {
        return String.format("User{name='%s', age=%s, email='%s'}", name, age, email);
    }

    public static void main(String... args) {
        new CommandLine(new User()).execute(args);
    }

    @Override
    public void run() {
        validate();

        // remaining business logic here
    }

    private void validate() {
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<User>> violations = validator.validate(this);

        if (!violations.isEmpty()) {
            String errorMsg = "";
            for (ConstraintViolation<User> violation : violations) {
                errorMsg += "ERROR: " + violation.getMessage() + "n";
            }
            throw new ParameterException(spec.commandLine(), errorMsg);
        }
    }
}

§10.2.4. Using a Custom Execution Strategy for Validation

The above JSR-380 BeanValidation can also be accomplished with a custom IExecutionStrategy that does the validation before executing the command. This moves the validation logic into a separate class. Picocli invokes this logic, removing the need to call a validate method from the business logic.

Such a custom execution strategy could look like this:

Java
Kotlin
javaclass ValidatingExecutionStrategy implements IExecutionStrategy {
    public int execute(ParseResult parseResult) {
        validate(parseResult.commandSpec());
        return new CommandLine.RunLast().execute(parseResult); // default execution strategy
    }

    void validate(CommandSpec spec) {
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<Object>> violations = validator.validate(spec.userObject());
        if (!violations.isEmpty()) {
            String errorMsg = "";
            for (ConstraintViolation<?> violation : violations) {
                errorMsg += "ERROR: " + violation.getMessage() + "n";
            }
            throw new ParameterException(spec.commandLine(), errorMsg);
        }
    }
}

The application can wire in this custom execution strategy as follows:

Java
Kotlin
java    public static void main(String... args) {
        new CommandLine(new MyApp())
                .setExecutionStrategy(new ValidatingExecutionStrategy())
                .execute(args);
    }

§11. Parser Configuration

§11.1. Case Sensitivity

By default, all options and subcommands are case sensitive. From picocli 4.3, case sensitivity is configurable. Case sensitivity can be switched off globally, as well as on a per-command basis.

To toggle case sensitivity for all commands, use the CommandLine::setSubcommandsCaseInsensitive and CommandLine::setOptionsCaseInsensitive methods. Use the CommandSpec::subcommandsCaseInsensitive and CommandSpec::optionsCaseInsensitive methods to give some commands a different case sensitivity than others.

Where possible, picocli will try to prevent ambiguity: when multiple options with the same name are registered in a command, a DuplicateOptionAnnotationsException is thrown. When multiple subcommands with the same name are registered in a command, a DuplicateNameException is thrown.

With case sensitivity switched off, the same principle applies: multiple options whose names differ only in case cannot be registered in a command. Similarly, multiple subcommands cannot be registered when their names differ only in case.

When a combination of POSIX options resembles a long option, picocli will prioritize the long option. This is the case regardless of case sensitivity, but be aware that with case sensitivity switched off, the chance of such collisions increases. For example, if a command has POSIX options -a, -b, and -c, and a long option -ABC, then, when the user specifies -abc, picocli will recognize it as the long option -ABC, not as the POSIX options.

§11.2. Abbreviated Options and Subcommands

Since picocli 4.4, the parser can recognize abbreviated options and subcommands. This needs to be enabled explicitly with CommandLine::setAbbreviatedOptionsAllowed and CommandLine::setAbbreviatedSubcommandsAllowed.

§11.2.1. Recognized Abbreviations

When abbreviations are enabled, users can specify the initial letter(s) of the first component and optionally of one or more subsequent components of an option or subcommand name.

"Components" are separated by - dash characters or by case, so for example, both --CamelCase and --kebab-case have two components.

When case sensitivity is disabled only the - dash character can be used to separate components.

Table 3. Examples of valid abbreviations
Option or Subcommand Sample Recognized Abbreviations

--veryLongCamelCase

--very, --vLCC, --vCase (…​)

--super-long-option

--sup, --sLO, --s-l-o, --s-lon, --s-opt, --sOpt (…​)

some-long-command

so, sLC, s-l-c, soLoCo, someCom (…​)

§11.2.2. Ambiguous Abbreviations

When the user specifies input that can match multiple options or subcommands, the parser throws a ParameterException. When applications use the execute method, an error message and the usage help is displayed to the user.

For example, given a command with subcommands help and hello, then ambiguous user input like hel will show this error message:

Error: 'hel' is not unique: it matches 'hello', 'help'

§11.2.3. Abbreviated Long Options and POSIX Clustered Short Options

When an argument can match both an abbreviated long option and a set of clustered short options, picocli matches the long option. This is the case regardless of abbreviations, but be aware that with abbreviated options enabled, the chance of such collisions increases.

For example:

Java
Kotlin
javaclass AbbreviationsAndPosix {
    @Option(names = "-A")      boolean a;
    @Option(names = "-B")      boolean b;
    @Option(names = "-AaaBbb") boolean aaaBbb;
}

AbbreviationsAndPosix app = new AbbreviationsAndPosix();
new CommandLine(app).setAbbreviatedOptionsAllowed(true).parseArgs("-AB");
assert app.aaaBbb == true; // the long option is matched from abbreviated input -AB
assert app.a == false;
assert app.b == false;

When abbreviated options are enabled, user input -AB will match the long -AaaBbb option, but not the -A and -B options.

§11.3. Overwriting Single Options

When a single-value option is specified multiple times on the command line, the default parser behaviour is to throw an OverwrittenOptionException. For example:

Java
Kotlin
java@Option(names = "-p") int port;

The following input results in an OverwrittenOptionException:

<command> -p 80 -p 8080

Applications can change this by calling CommandLine::setOverwrittenOptionsAllowed with true before parsing the input. When overwritten options are allowed, the last specified value takes effect (the above input will set the port field to 8080) and a WARN level message is printed to the console. (See Tracing for how to switch off the warnings.)

§11.4. Stop At Positional

By default, positional parameters can be mixed with options on the command line, but this is not always desirable. From picocli 2.3, applications can call CommandLine::setStopAtPositional with true to force the parser to treat all values following the first positional parameter as positional parameters.

When this flag is set, the first positional parameter effectively serves as an "end of options" marker.

§11.5. Stop At Unmatched

From picocli 2.3, applications can call CommandLine::setStopAtUnmatched with true to force the parser to stop interpreting options and positional parameters as soon as it encounters an unmatched argument.

When this flag is set, the first unmatched argument and all subsequent command line arguments are added to the unmatched arguments list returned by CommandLine::getUnmatchedArguments.

§11.6. Unmatched Input

By default, an UnmatchedArgumentException is thrown when a command line argument cannot be assigned to an option or positional parameter. For example:

Java
Kotlin
javaclass OnlyThree {
    @Parameters(arity = "3") String[] values;
}

The command has only one annotated field, values, and it expects exactly three arguments, so the following input results in an UnmatchedArgumentException:

java OnlyThree 1 2 3 4 5

Applications can change this by calling CommandLine::setUnmatchedArgumentsAllowed with true before parsing the input. When unmatched arguments are allowed, the above input will be accepted and a WARN level message is printed to the console. (See Tracing for how to switch off the warnings.)

The unmatched argument values can be obtained with the CommandLine::getUnmatchedArguments method.

§11.7. @Unmatched annotation

As of picocli 3.0, fields annotated with @Unmatched will be populated with the unmatched arguments. The field must be of type String[] or List<String>.

If picocli finds a field annotated with @Unmatched, it automatically sets unmatchedArgumentsAllowed to true so no UnmatchedArgumentException is thrown when a command line argument cannot be assigned to an option or positional parameter. If no unmatched arguments are found, the value of the field annotated with @Unmatched is unchanged.

§11.8. Unknown Options

§11.8.1. Unknown Options Definition

A special case of unmatched input are arguments that resemble options but don’t match any of the defined options. Picocli determines if a value "resembles an option" by comparing its leading characters to the prefix characters of the known options.

Negative numbers are not considered to be unknown options, so values like -123, -NaN, -Infinity, -#ABC and -0xCAFEBABE will not be treated specially for resembling an option name.

For example, the value -z is considered an unknown option when we have a command that only defines options -x and -y:

Java
Kotlin
java@Option(names = "-x") String x;
@Option(names = "-y") String y;
@Parameters String[] remainder;

The above defines options -x and -y, but no option -z. So what should the parser do when the user gives input like this?

<command> -z -x XXX

§11.8.2. Positional Parameters Resembling Options

One possibility is to silently accept such values as positional parameters, but this is often not desirable.

By default, when the value resembles an option, picocli throws an UnmatchedArgumentException rather than treating it as a positional parameter.

Picocli 3.0 introduced the CommandLine::setUnmatchedOptionsArePositionalParams method that can be used to force the parser to treat arguments resembling an option as positional parameters. For example:

<command> -z -x XXX

When unmatchedOptionsArePositionalParams is set to true, the unknown option -z is treated as a positional parameter. The next argument -x is recognized and processed as a known option like you would expect.

An alternative is to call CommandLine::setUnmatchedArgumentsAllowed with true, this will accept and store such values separately as described in Unmatched Input.

§11.8.3. Option Parameters Resembling Options

By default, options accept parameter values that "resemble" (but don’t exactly match) an option.

Picocli 4.4 introduced a CommandLine::setUnmatchedOptionsAllowedAsOptionParameters method that makes it possible to configure the parser to reject values that resemble options as option parameters. Setting this to false will result in values resembling option names being rejected as option values.

For example:

Java
Kotlin
javaclass MyApp {
    @Option(names = "-x") String x;
}

By default, a value like -z, which resembles an option, is accepted as the parameter for -x:

Java
Kotlin
javaMyApp app = new MyApp();
new CommandLine(app).parseArgs("-x", "-z");
assert "-z".equals(app.x);

After setting the unmatchedOptionsAllowedAsOptionParameters parser option to false, values resembling an option are rejected as parameter for -x:

Java
Kotlin
javanew CommandLine(new MyApp())
        .setUnmatchedOptionsAllowedAsOptionParameters(false)
        .parseArgs("-x", "-z");

This will throw an UnmatchedArgumentException with message:

"Unknown option: '-z'; Expected parameter for option '-x' but found '-z'"

§11.9. Option Names or Subcommands as Option Values

§11.9.1. By Default Options Do Not Consume Option Names or Subcommands

Since picocli 4.4, the parser will no longer assign values that match a subcommand name or an option name to options that take a parameter, unless the value is in quotes. For example:

Java
Kotlin
javaclass App {
    @Option(names = "-x") String x;
    @Option(names = "-y") String y;

    public static void main(String... args) {
        App app = new App();
        new CommandLine(app).setTrimQuotes(true).parseArgs(args);
        System.out.printf("x='%s', y='%s'%n", app.x, app.y);
    }
}

In previous versions of picocli, the above command would accept input -x -y, and the value -y would be assigned to the x String field. As of picocli 4.4, the above input will be rejected with an error message indicating that the -x option requires a parameter.

If it is necessary to accept values that match option names, such values need to be quoted. For example:

bashjava App -x="-y"

This will print the following output:

bashx='-y', y='null'

§11.9.2. Enable Consuming Option Names or Subcommands

Picocli 4.7.0 introduces two parser configuration options to change this behaviour:

  • CommandLine::setAllowOptionsAsOptionParameters allows options to consume option names

  • CommandLine::setAllowSubcommandsAsOptionParameters allows options to consume subcommand names

When set to true, all options in the command (options that take a parameter) can consume values that match option names or subcommand names.

This means that any option will consume the maximum number of arguments possible for its arity.

USE WITH CAUTION!

If an option is defined as arity = "*", this option will consume all remaining command line arguments following this option (until the End-of-options delimiter) as parameters of this option.

§11.9.3. Custom Parsing for Option-Specific Behaviour

The parser configuration options in the previous section apply to all options in the command.

Some applications may want to enable options consuming option names or subcommands for some options, but not for all options in the command. Such applications can replace or augment picocli’s parser by doing custom parameter processing for such options. For example:

Java
Kotlin
javaclass App implements Runnable {
    @Option(names = "-x", parameterConsumer = App.CustomConsumer.class)
    String x;

    @Option(names = "-y")
    String y;

    @Command
    public void mySubcommand() {}

    static class CustomConsumer implements IParameterConsumer {
        @Override
        public void consumeParameters(Stack<String> args, ArgSpec argSpec, CommandSpec cmdSpec) {
            if (args.isEmpty()) {
                throw new ParameterException(cmdSpec.commandLine(),
                        "Error: option '-x' requires a parameter");
            }
            String arg = args.pop();
            argSpec.setValue(arg);
        }
    }

    public void run() {
        System.out.printf("x='%s', y='%s'%n", x, y);
    }

    public static void main(String... args) {
        new CommandLine(new App()).execute(args);
    }
}

The above code assigns whatever command line argument that follows the -x option to that option, and allows input like the following:

bashjava App -x=mySubcommand
java App -x mySubcommand
java App -x=-y
java App -x -y -y=123

§11.10. Toggle Boolean Flags

When a flag option is specified on the command line picocli will set its value to the opposite of its default value.

Prior to 4.0, the default was to "toggle" boolean flags to the opposite of their current value: if the previous value was true it is set to false, and when the value was false it is set to true.

Applications can call CommandLine::setToggleBooleanFlags with true to enable toggling. Note that when toggling is enabled, specifying a flag option twice on the command line will have no effect because they cancel each other out.

§11.11. POSIX Clustered Short Options

By default, the picocli parser allows POSIX clustered short options, so short options like -x -v -f SomeFile can be clustered together like -xvfSomeFile. From picocli 3.0, applications can call CommandLine::setPosixClusteredShortOptionsAllowed with false to enforce that options must be separated with whitespace on the command line. (This also means that option parameters must be separated from the option name by whitespace or the = separator character, so -D key=value and -D=key=value will be recognized but -Dkey=value will not.)

§11.12. Lenient Mode

From picocli 3.2, the parser can be configured to continue parsing invalid input to the end. When collectErrors is set to true, and a problem occurs during parsing, an Exception is added to the list returned by ParseResult::errors and parsing continues. The default behaviour (when collectErrors is false) is to abort parsing by throwing the Exception.

This is useful when generating completion candidates on partial input, and is also useful when using picocli in languages like Clojure where idiomatic error handling does not involve throwing and catching exceptions.

When using this feature, applications are responsible for actively verifying that no errors occurred before executing the business logic. Use with care!

§11.13. Quoted Values

§11.13.1. Trimming Quotes

From picocli 3.7, quotes around command line parameters are preserved by default (previously they were removed). This can be configured with CommandLine::setTrimQuotes, or the parser configuration trimQuotes. From picocli 4.0, quoted arguments can contain nested quoted substrings, to give end users fine-grained control over how values are split.

If CommandLine::setTrimQuotes, or the parser configuration trimQuotes is set to true, picocli will remove quotes from the command line arguments, as follows:

  • As each command line argument is processed, the below smart unquote procedure is used to trim the outermost quotes.

  • Next, if the option or positional parameter has a split regex defined, the parameter value is split while respecting quotes: the split regex is not matched if it occurs in a quoted substring of the parameter value. Each of the parts found by the splitting process will have its quotes removed using the below "smart unquote" procedure.

See the Splitting Quoted Parameters section below for examples.

§Smart Unquote
  • If the command line argument contains just the leading and trailing quote, these quotes are removed.

  • If the command line argument contains unescaped quotes, other than the leading and trailing quote, the argument is unchanged (the leading and trailing quotes remain).

  • If a quoted command line argument contains backslash-escaped quotes, the leading and trailing quotes are removed, backslash-escaped quotes are converted to unescaped quotes, and backslash-escaped backslashes are converted to unescaped backslashes.

For example:

Command Line Argument After Trimming Quotes Note

"-x=abc"

-x=abc

quotes removed

"a,b","x,y"

"a,b","x,y"

left unchanged

"-x=a,b,"c,d,e",f"

-x=a,b,"c,d,e",f

Splitting will find 4 values: a; b; c,d,e; and f

"-x="a,b,"c,d,e",f""

-x="a,b,"c,d,e",f"

Splitting will find 1 value: a,b,"c,d,e",f

§11.13.2. Splitting Quoted Parameters

By default, if the option or positional parameter has a split regex defined, parameter values are split into parts while respecting quotes: the split regular expression is not matched inside a quoted region.

Example:

Java
Kotlin
java@Option(names = "-x", split = ",")
String[] parts;

Given input like below:

<command> -x "-Dvalues=a,b,c","-Dother=1,2"

This results in the parts array having the following values, assuming the parser configuration trimQuotes is false (the default):

"-Dvalues=a,b,c"
"-Dother=1,2"

If the parser configuration trimQuotes is true, the above example would be split into the following values (with quotes trimmed from the resulting parts):

-Dvalues=a,b,c
-Dother=1,2

Given input like below:

<command> -x a,b,"c,d,e",f,"xxx,yyy"

This results in the parts array having the following values:

a
b
"c,d,e"
f
"xxx,yyy"

Or, if the parser configuration trimQuotes is true:

a
b
c,d,e
f
xxx,yyy

To preserve quotes when trimQuotes is true, specify additional nested quotes on the command line. For example:

<command> "-x="a,b,"c,d,e",f"" "x,y,z" ""1,2,3"" ""1,2,3""

With parser configuration trimQuotes set to true, the above input gives the following values:

a,b,"c,d,e",f
x
y
z
1,2,3
"1,2,3"

This "smart splitting" (respecting quotes) can be switched off with CommandLine::setSplitQuotedStrings: setting the splitQuotedStrings parser attribute to true switches off smart splitting, and the split regex is applied to the parameter value regardless of quotes.

splitQuotedStrings is mostly for backwards compatibility, for applications that want the pre-3.7 behaviour of simply splitting regardless of quotes. Most applications should leave this setting to the default (false). When this setting is true, the above input is parsed as:

a
b
"c
d
e"
f
"xxx
yyy"

§11.14. Customizing Negatable Options

Negatable options can be customized via the INegatableOptionTransformer interface:

javainterface INegatableOptionTransformer {
    /**
     * Returns the negative form of the specified option name for the parser to recognize
     * when parsing command line arguments.
     * @param optionName the option name to create a negative form for,
     *                   for example {@code --force}
     * @param cmd the command that the option is part of
     * @return the negative form of the specified option name, for example {@code --no-force}
     */
    String makeNegative(String optionName, CommandSpec cmd);

    /**
     * Returns the documentation string to show in the synopsis and usage help message for
     * the specified option. The returned value should be concise and clearly suggest that
     * both the positive and the negative form are valid option names.
     * @param optionName the option name to create a documentation string for,
     *                   for example {@code --force}, or {@code -XX:+<option>}
     * @param cmd the command that the option is part of
     * @return the documentation string for the negatable option,
     *         for example {@code --[no-]force}, or {@code -XX:(+|-)<option>}
     */
    String makeSynopsis(String optionName, CommandSpec cmd);
}

This allows you to control:

  • which option names should have a negative form

  • the negative form recognized by the parser while parsing the command line

  • the documentation string showing both the positive and the negative form in the usage help message

By default, a set of regular expressionshttps://picocli.info/apidocs-all/info.picocli/picocli/CommandLine.RegexTransformer.html#createDefault() is used to control the above. Use CommandLine::setNegatableOptionTransformer to replace the INegatableOptionTransformerhttps://picocli.info/apidocs-all/info.picocli/picocli/CommandLine.INegatableOptionTransformer with a custom version. See the JavaDoc for details.

§11.15. Custom Parameter Processing

As of version 4.6, picocli offers two different parser plugins for custom parameter processing: while the IParameterPreprocessor is a powerful and flexible tool, the IParameterConsumer serves as a simpler alternative.

§11.15.1. IParameterConsumer Parser Plugin

Options or positional parameters can be assigned an IParameterConsumer that implements custom logic to process the parameters for this option or this position. When an option or positional parameter with a custom IParameterConsumer is matched on the command line, picocli’s internal parser is temporarily suspended, and the custom parameter consumer becomes responsible for consuming and processing as many command line arguments as needed.

This can be useful when passing options through to another command.

For example, the unix findhttps://en.wikipedia.org/wiki/Find_(Unix) command has a -exechttps://en.wikipedia.org/wiki/Find_(Unix)#Execute_an_action option to execute some action for each file found. Any arguments following the -exec option until a ; or + argument are not options for the find command itself, but are interpreted as a separate command and its options.

The example below demonstrates how to implement find -exec using this API:

Java
Kotlin
java@Command(name = "find")
class Find {
    @Option(names = "-exec", parameterConsumer = ExecParameterConsumer.class)
    List<String> list = new ArrayList<String>();
}

class ExecParameterConsumer implements IParameterConsumer {
    public void consumeParameters(Stack<String> args, ArgSpec argSpec, CommandSpec commandSpec) {
        List<String> list = argSpec.getValue();
        while (!args.isEmpty()) {
            String arg = args.pop();
            list.add(arg);

            // `find -exec` semantics: stop processing after a ';' or '+' argument
            if (";".equals(arg) || "+".equals(arg)) {
                break;
            }
        }
    }
}
Make sure any nested classes are static, or picocli will not be able to instantiate them.

§11.15.2. IParameterPreprocessor Parser Plugin

Introduced in picocli 4.6, the IParameterPreprocessor is also a parser plugin, similar to IParameterConsumer, but more flexible.

Options, positional parameters and commands can be assigned an IParameterPreprocessor that implements custom logic to preprocess the parameters for this option, position or command. When an option, positional parameter or command with a custom IParameterPreprocessor is matched on the command line, picocli’s internal parser is temporarily suspended, and this custom logic is invoked.

This custom logic may completely replace picocli’s internal parsing for this option, positional parameter or command, or augment it by doing some preprocessing before picocli’s internal parsing is resumed for this option, positional parameter or command.

The "preprocessing" actions can include modifying the stack of command line parameters, or modifying the model.

§Example use case

This may be useful when disambiguating input for commands that have both a positional parameter and an option with an optional parameter. For example, suppose we have a command with the following synopsis:

edit [--open[=<editor>]] <file>

One of the limitations of options with an optional parameter is that they are difficult to combine with positional parameters.

With a custom parser plugin, we can customize the parser, such that VALUE in --option=VALUE is interpreted as the option parameter, and in --option VALUE (without the = separator), VALUE is interpreted as the positional parameter. The code below demonstrates this:

Java
Kotlin
java@Command(name = "edit")
class Edit {

    @Parameters(index = "0", arity="0..1", description = "The file to edit.")
    File file;

    enum Editor { defaultEditor, eclipse, idea, netbeans }

    @Option(names = "--open", arity = "0..1", preprocessor = Edit.MyPreprocessor.class,
        description = {
           "Optionally specify the editor to use (${COMPLETION-CANDIDATES}). " +
           "If omitted the default editor is used. ",
           "Example: edit --open=idea FILE opens IntelliJ IDEA (notice the '=' separator)",
           "         edit --open FILE opens the specified file in the default editor"
        })
    Editor editor = Editor.defaultEditor;

    static class MyPreprocessor implements IParameterPreprocessor {
        public boolean preprocess(Stack<String> args,
                                  CommandSpec commandSpec,
                                  ArgSpec argSpec,
                                  Map<String, Object> info) {
            // we need to decide whether the next arg is the file to edit
            // or the name of the editor to use...
            if (" ".equals(info.get("separator"))) { // parameter was not attached to option

                // act as if the user specified --open=defaultEditor
                args.push(Editor.defaultEditor.name());
            }
            return false; // picocli's internal parsing is resumed for this option
        }
    }
}
Make sure any nested classes are static, or picocli will not be able to instantiate them.

With this preprocessor in place the user can now specify his editor of choice (e.g. --open=idea). If no editor is given, the default editor is used:

# User input # Command State # -------------------------- --open A B # editor: defaultEditor, file: A, unmatched: [B] --open A # editor: defaultEditor, file: A, unmatched: [] --open=A B # editor: A, file: B, unmatched: [] --open=A # editor: A, file: null, unmatched: []

§11.15.3. Parser Plugin Comparison

Table 4. Comparison of IParameterPreprocessor and IParameterConsumer parser plugins.
IParameterPreprocessor IParameterConsumer

Summary

Either augment (return false) or replace (return true) picocli parsing logic for the matched element.

Replaces picocli parsing logic for the matched element.

Scope

Commands as well as options and positional parameters.

Options and positional parameters only.

Read parser state

Yes, information may be received via the info map

No

Modify parser state

Yes, via modifying the info map

No

§12. Help

§12.1. Help Options

Applications can define help options by setting attribute versionHelp = true, usageHelp = true or help = true. If one of the arguments specified on the command line is a "help" option, picocli will not throw a MissingParameterException when required options are missing.

For example:

Java
Kotlin
java@Option(names = {"-V", "--version"}, versionHelp = true, description = "display version info")
boolean versionInfoRequested;

@Option(names = {"-h", "--help"}, usageHelp = true, description = "display this help message")
boolean usageHelpRequested;

Use these attributes for options that request the usage help message or version information to be shown on the console.

Java
Kotlin
javaApp app = CommandLine.populateCommand(new App(), args);
if (app.usageHelpRequested) {
    CommandLine.usage(new App(), System.out);
    return;
}

The CommandLine class offers two methods that allow external components to detect whether usage help or version information was requested (without inspecting the annotated domain object):

  • CommandLine::isUsageHelpRequested returns true if the parser matched an option annotated with usageHelp=true

  • CommandLine::isVersionHelpRequested returns true if the parser matched an option annotated with versionHelp=true

Java
Kotlin
javaCommandLine commandLine = new CommandLine(new App());
commandLine.parseArgs(args);
if (commandLine.isUsageHelpRequested()) {
    commandLine.usage(System.out);
    return;
} else if (commandLine.isVersionHelpRequested()) {
    commandLine.printVersionHelp(System.out);
    return;
}
// ... run App's business logic

§12.2. Mixin Standard Help Options

Picocli 3.0 introduced the mixinStandardHelpOptions command attribute. When this attribute is set to true, picocli adds a mixin to the command that adds usageHelp and versionHelp options to the command. For example:

Java
Kotlin
java@Command(mixinStandardHelpOptions = true, version = "auto help demo - picocli 3.0")
class AutoHelpDemo implements Runnable {

    @Option(names = "--option", description = "Some option.")
    String option;

    @Override public void run() { /* ... */ }
}

Commands with mixinStandardHelpOptions do not need to explicitly declare fields annotated with @Option(usageHelp = true) and @Option(versionHelp = true) any more. The usage help message for the above example looks like this:

Usage: <main class> [-hV] [--option=<option>] --option=<option> Some option. -h, --help Show this help message and exit. -V, --version Print version information and exit.

§12.3. Built-in Help Subcommand

As of version 3.0, picocli provides a help subcommand (picocli.CommandLine.HelpCommand) that can be installed as a subcommand on any application command. It prints usage help for the parent command or sibling subcommands. For example:

Java
Kotlin
javaimport picocli.CommandLine.HelpCommand;

@Command(name = "myapp", subcommands = {HelpCommand.class, Subcommand.class})
class MyCommand implements Runnable {
    // ...
}

For example, the following command prints usage help for a subcommand:

bashmyapp help subcommand

To print usage help for the main command:

bashmyapp help

§12.4. Custom Help Subcommands

Custom help subcommands should mark themselves as a help command to tell picocli not to throw a MissingParameterException when required options are missing.

java@Command(helpCommand = true)

Picocli 4.0 introduced a new interface picocli.CommandLine.IHelpCommandInitializable2 that provides custom help commands with access to the parent command and sibling commands, whether to use Ansi colors or not, and the streams to print the usage help message to.

The IHelpCommandInitializable2 interface replaces the IHelpCommandInitializable interface which was introduced in picocli 3.0.

javapublic interface IHelpCommandInitializable2 {
    /**
     * Initializes this object with the information needed to implement a help command that
     * provides usage help for other commands.
     *
     * @param helpCommandLine the {@code CommandLine} object associated with this help command.
      *                       Implementors can use this to walk the command hierarchy and
      *                       get access to the help command's parent and sibling commands.
     * @param colorScheme the color scheme to use when printing help, including whether
     *                    to use Ansi colors or not
     * @param outWriter the output writer to print the usage help message to
     * @param errWriter the error writer to print any diagnostic messages to,
     *                  in addition to the output from the exception handler
     */
    void init(CommandLine helpCommandLine,
              Help.ColorScheme colorScheme,
              PrintWriter outWriter,
              PrintWriter errWriter);
}

§12.5. Printing Help Automatically

As of picocli 2.0, the convenience methods will automatically print usage help and version information when a help option was specified on the command line (options annotated with the versionHelp or usageHelp attribute - but not the help attribute).

The same holds for the mixinStandardHelpOptions attribute, the built-in HelpCommand and any custom help subcommands marked as a help command.

The following convenience methods automatically print help:

  • CommandLine::execute

  • CommandLine::call

  • CommandLine::run

  • CommandLine::invoke

  • CommandLine::parseWithHandler (with the built-in Run…​ handlers)

  • CommandLine::parseWithHandlers (with the built-in Run…​ handlers)

The following methods do not automatically print help:

  • CommandLine::parse

  • CommandLine::parseArgs

  • CommandLine::populateCommand

When using the last three methods, applications need to query the parse result to detect whether usage help or version help was requested, and invoke CommandLine::usage or CommandLine::printVersionHelp to actually print the requested help message.

§13. Version Help

§13.1. Static Version Information

§13.1.1. Command version Attribute

As of picocli 0.9.8, applications can specify version information in the version attribute of the @Command annotation.

Java
Kotlin
java@Command(version = "1.0")
class VersionedCommand {
    @Option(names = { "-V", "--version" }, versionHelp = true,
            description = "print version information and exit")
    boolean versionRequested;
    /* ... */ }

The CommandLine.printVersionHelp(PrintStream) method extracts the version information from this annotation and prints it to the specified PrintStream.

Java
Kotlin
javaCommandLine commandLine = new CommandLine(new VersionedCommand());
commandLine.parseArgs(args);
if (commandLine.isVersionHelpRequested()) {
    commandLine.printVersionHelp(System.out);
    return;
}

§13.1.2. Multi-line Version Info

The version may specify multiple Strings. Each will be printed on a separate line.

Java
Kotlin
java@Command(version = { "Versioned Command 1.0", "Build 12345", "(c) 2017" })
class VersionedCommand { /* ... */ }

The CommandLine.printVersionHelp(PrintStream) method will print the above as:

Versioned Command 1.0 Build 12345 (c) 2017

§13.1.3. Version Info With Variables

As of picocli 4.0, the version strings may contain system properties and environment variables. For example:

Java
Kotlin
java@Command(version = {
    "Versioned Command 1.0",
    "Picocli " + picocli.CommandLine.VERSION,
    "JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})",
    "OS: ${os.name} ${os.version} ${os.arch}"})
class VersionedCommand { /* ... */ }

Depending on your environment, that may print something like:

Versioned Command 1.0 Picocli 4.0.0 JVM: 1.8.0_202 (Oracle Corporation Substrate VM GraalVM 1.0.0-rc15 CE) OS: Linux 4.4.0-17134-Microsoft amd64

§13.1.4. Version Info With Colors

The version strings may contain markup to show ANSI styles and colors. For example:

Java
Kotlin
java@Command(version = {
        "@|yellow Versioned Command 1.0|@",
        "@|blue Build 12345|@",
        "@|red,bg(white) (c) 2017|@" })
class VersionedCommand { /* ... */ }

The markup will be rendered as ANSI escape codes on supported systems.

Image (Asset 2/17) alt= Pre-defined Styles Pre-defined Colors

bold

black

faint

red

underline

green

italic

yellow

blink

blue

reverse

magenta

reset

cyan

white

Colors are applied as foreground colors by default. You can set background colors by specifying bg(<color>). For example, @|bg(red) text with red background|@. Similarly, fg(<color>) explicitly sets the foreground color.

The example below shows how this markup can be used to add colors and styles to the headings and descriptions of a usage help message:

Java
Kotlin
java@Command(name = "commit",
        sortOptions = false,
        headerHeading = "@|bold,underline Usage|@:%n%n",
        synopsisHeading = "%n",
        descriptionHeading = "%n@|bold,underline Description|@:%n%n",
        parameterListHeading = "%n@|bold,underline Parameters|@:%n",
        optionListHeading = "%n@|bold,underline Options|@:%n",
        header = "Record changes to the repository.",
        description = "Stores the current contents of the index in a new commit " +
                "along with a log message from the user describing the changes.")
class GitCommit { /* ... */ }
Markup styles cannot be nested, for example: @|bold this @|underline that|@|@ will not work. You can achieve the same by combining styles, for example: @|bold this|@ @|bold,underline that|@ will work fine.

As of picocli 4.2, custom markup like @|bold mytext|@, @|italic mytext|@ etc. can also be converted to custom markup like <b>mytext</b> and <i>mytext</i> in HTML, or *mytext* and _mytext_ in lightweight markup languages like AsciiDoc. Applications can control this by setting a ColorScheme with a custom markup map. This feature is used to generate man page documentation.

§15.3. Styles and Colors in Application Output

The use of ANSI colors and styles is not limited to the usage help and version information.

Applications can use the picocli Ansi class directly to create colored output. By using the Ansi.AUTO enum value, picocli will auto-detect whether it can emit ANSI escape codes or only the plain text.

Java
Kotlin
javaimport picocli.CommandLine.Help.Ansi;
// ...
String str = Ansi.AUTO.string("@|bold,green,underline Hello, colored world!|@");
System.out.println(str);

§15.4. More Colors

0x00-0x07: standard colors (the named colors) 0x08-0x0F: high intensity colors (often similar to named colors + bold style) 0x10-0xE7: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5) 0xE8-0xFF: grayscale from black to white in 24 steps

Colors from the 256 color palette can be specified by their index values or by their RGB components. RGB components must be separated by a semicolon ; and each component must be between 0 and 5, inclusive.

For example, @|bg(0;5;0) text with red=0, green=5, blue=0 background|@, or @|fg(46) the same color by index, as foreground color|@.

Image (Asset 3/17) alt= Variable Since Use in Meaning

${DEFAULT-VALUE}

3.2

the description for an option or positional parameter

replaced with the default value for that option or positional parameter

${FALLBACK-VALUE}

4.0

the description for an option with optional parameter

replaced with the fallback value for that option or positional parameter

${MAP-FALLBACK-VALUE}

4.6

the description for map option or positional parameter that allows key-only parameters

replaced with the map fallback value for that option or positional parameter

${COMPLETION-CANDIDATES}

3.2

the description for an option or positional parameter

replaced with the completion candidates for that option or positional parameter

${COMMAND-NAME}

4.0

any section of the usage help message for a command

replaced with the name of the command

${COMMAND-FULL-NAME}

4.0

any section of the usage help message for a command

replaced with the fully qualified name of the command (that is, preceded by its parent fully qualified name)

${PARENT-COMMAND-NAME}

4.0

any section of the usage help message for a command

replaced with the name of its parent command

${PARENT-COMMAND-FULL-NAME}

4.0

any section of the usage help message for a command

replaced with the fully qualified name of its parent command (that is, preceded by the name(s) of the parent command’s ancestor commands)

${ROOT-COMMAND-NAME}

4.4

any section of the usage help message for a command

replaced with the name of the top-level command (in a command suite with subcommands)

§20.3. Custom Variables

In addition, you can define your own variables. Currently the following syntaxes are supported:

  • ${sys:key}: system property lookup, replaced by the value of System.getProperty("key")

  • ${env:key}: environment variable lookup, replaced by the value of System.getEnv("key")

  • ${bundle:key}: look up the value of key in the resource bundle of the command

  • ${key}: search all of the above, first system properties, then environment variables, and finally the resource bundle of the command

§20.4. Default Values for Custom Variables

You can specify a default value to use when no value is found for a custom variable. The syntax for specifying a default is ${a:-b}, where a is the variable name and b is the default value to use if a is not found.

So, for the individual lookups, this looks like this:

${key:-defaultValue}
${sys:key:-defaultValue}
${env:key:-defaultValue}
${bundle:key:-defaultValue}

The default value may contain other custom variables. For example:

${bundle:a:-${env:b:-${sys:c:-X}}}

The above variable is expanded as follows. First, try to find key a in the command’s resource bundle. If a is not found in the resource bundle, get the value of environment variable b. If no environment variable b exists, get the value of system property c. Finally, if no system property c exists, the value of the expression becomes X.

§20.5. Escaping Variables

Sometimes you want to show a string like "${VAR}" in a description. A $ character can be escaped with another $ character. Therefore, $${VAR} will not be interpreted as a VAR variable, but will be replaced by ${VAR} instead.

§20.6. Switching Off Variable Interpolation

Variable interpolation can be switched off for the full command hierarchy by calling CommandLine.setInterpolateVariables(false), or for a particular command by calling CommandSpec.interpolateVariables(false).

§20.7. Limitations of Variable Interpolation

Some attribute values need to be resolved early, when the model is constructed from the annotation values.

Specifically:

  • command names and aliases, option names, mixin names

  • arity (for options and positional parameters)

  • index (for positional parameters)

  • separator (for commands)

It is possible for these attributes to contain variables, but be aware of the limitations.

If these attributes have variables, and the variables get a different value after the model is constructed, the change will not be reflected in the model.

§21. Tips & Tricks

§21.1. Programmatic API

Picocli also provides a programmatic API, in addition to the annotations API.

The programmatic API allows applications to dynamically create command line options on the fly, where not all options are known in advance. It also makes it possible to use picocli in other JVM languages that do not support annotations.

Another use case is creating idiomatic domain-specific languages for processing command line arguments. For example, Groovy’s CliBuilder DSLhttps://groovy-lang.org/dsls.html#_clibuilder is implemented using picocli’s programmatic API.

§21.2. @Option and @Parameters Methods

As of version 3.2, @Option and @Parameters annotations can be added to methods as well as fields of a class.

For concrete classes, annotate "setter" methods (methods that accept a parameter) and when the option is specified on the command line, picocli will invoke the method with the value specified on the command line, converted to the type of the method parameter.

Alternatively, you may annotate "getter-like" methods (methods that return a value) on an interface, and picocli will create an instance of the interface that returns the values specified on the command line, converted to the method return type.

§21.2.1. Annotating Methods of an Interface

The @Option and @Parameters annotations can be used on methods of an interface that return a value. For example:

Java
Kotlin