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
javainterface Counter {
    @Option(names = "--count")
    int getCount();
}

You use it by specifying the class of the interface:

Java
Kotlin
javaCommandLine cmd = new CommandLine(Counter.class); // specify a class
String[] args = new String[] {"--count", "3"};
cmd.parseArgs(args);
Counter counter = cmd.getCommand(); // picocli created an instance
assert counter.getCount() == 3; // method returns command line value

§21.2.2. Annotating Methods of a Concrete Class

The @Option and @Parameters annotations can be used on methods of a class that accept a parameter. For example:

Java
Kotlin
javaclass Counter {
    int count;

    @Option(names = "--count")
    void setCount(int count) {
        this.count = count;
    }
}

You use it by passing an instance of the class:

Java
Kotlin
javaCounter counter = new Counter(); // the instance to populate
CommandLine cmd = new CommandLine(counter);
String[] args = new String[] {"--count", "3"};
cmd.parseArgs(args);
assert counter.count == 3; // method was invoked with command line value

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

Java
Kotlin
javaclass ValidationExample {
    private Map<String, String> properties = new LinkedHashMap<>();

    @Spec private CommandSpec spec; // injected by picocli

    @Option(names = {"-D", "--property"}, paramLabel = "KEY=VALUE")
    public void setProperty(Map<String, String> map) {
        for (String key : map.keySet()) {
            String newValue = map.get(key);
            validateUnique(key, newValue);
            properties.put(key, newValue);
        }
    }

    private void validateUnique(String key, String newValue) {
        String existing = properties.get(key);
        if (existing != null && !existing.equals(newValue)) {
            throw new ParameterException(spec.commandLine(),
                    String.format("Duplicate key '%s' for values '%s' and '%s'.",
                    key, existing, newValue));
        }
    }
    // ...
}

Applications that reuse a single CommandLine instance for multiple parseArgs or execute invocations should be aware of the following:

Annotated setter method options that explicitly set defaultValue = Option.NULL_VALUE will get a call with a null parameter when the command line did not include that option.

If the setter method annotation does not explicitly set defaultValue = Option.NULL_VALUE, then this method will not be called when the command line did not include that option. This means that the value will retain the value of the previous parseArgs or execute invocation, which may not be desirable. (This only happens for applications that reuse a single CommandLine instance for multiple parseArgs or execute invocations.)

Java
Kotlin
javaclass App {
    String x;
    String y;

    // reset to null when option not specified
    @Option(names = "-x", defaultValue = Option.NULL_VALUE)
    void setX(String x) { this.x = x; }

    // retains value of previous parseArgs invocation when option not specified
    @nption(names = "-y")
    void setY(String y) { this.y = y; }
}

§21.3. @Command Methods

As of picocli 3.6, methods can be annotated with @Command. The method parameters provide the command options and parameters. For example:

Java
Kotlin
javaclass Cat {
    public static void main(String[] args) {
        Method doit = CommandLine.getCommandMethods(Cat.class, "cat").get(0);
        CommandLine cmd = new CommandLine(doit);
        int exitCode = cmd.execute(args);
    }

    @Command(description = "Concatenate FILE(s) to standard output.",
             mixinStandardHelpOptions = true, version = "4.1.3")
    int cat(@Option(names = {"-E", "--show-ends"}) boolean showEnds,
             @Option(names = {"-n", "--number"}) boolean number,
             @Option(names = {"-T", "--show-tabs"}) boolean showTabs,
             @Option(names = {"-v", "--show-nonprinting"}) boolean showNonPrinting,
             @Parameters(paramLabel = "FILE") File[] files) {
        // process files
        return CommandLine.ExitCode.OK;
    }
}

The usage help of the above command looks like this:

Usage: cat [-EhnTvV] [FILE...] Concatenate FILE(s) to standard output. [FILE...] -E, --show-ends -h, --help Show this help message and exit. -n, --number -T, --show-tabs -v, --show-nonprinting -V, --version Print version information and exit.

See below for an example that uses a resource bundle to define usage help descriptions outside the code.

For positional parameters, the @Parameters annotation may be omitted on method parameters.

If compiled with the -parameters flag on Java 8 or higher, the paramLabel of positional parameters is obtained from the method parameter name using reflection instead of the generic arg0, arg1, etc.

§21.3.1. Subcommand Methods

If the enclosing class is annotated with @Command, method commands are automatically added as subcommands to the class command, unless the class command has attribute @Command(addMethodSubcommands = false). For example:

Java
Kotlin
java@Command(name = "git", mixinStandardHelpOptions = true, version = "picocli-4.1.3",
         resourceBundle = "Git_Messages", subcommands = { HelpCommand.class })
class Git {
    @Option(names = "--git-dir", descriptionKey = "GITDIR")
    Path path;

    @Command
    void commit(@Option(names = {"-m", "--message"}) String commitMessage,
                @Option(names = "--squash", paramLabel = "<commit>") String squash,
                @Parameters(paramLabel = "<file>") File[] files) {
        // ... implement business logic
    }
}

Use @Command(addMethodSubcommands = false) on the class @Command annotation if the @Command-annotated methods in this class should not be added as subcommands.

§21.3.2. Description Text in ResourceBundle

The usage help of the above git commit example command is very minimal:

Usage: git commit [--squash=<commit>] [-m=<arg0>] [<file>...] [<file>...] --squash=<commit> -m, --message=<arg0>

You can use a resource bundle to move the descriptions out of the code:

# shared between all commands help = Show this help message and exit. version = Print version information and exit. # command-specific strings git.usage.description = Version control system git.GITDIR = Set the path to the repository git.commit.usage.description = Record changes to the repository git.commit.message = Use the given <msg> as the commit message. git.commit.squash = Construct a commit message for use with rebase --autosquash. git.commit.<file>[0..*] = The files to commit.

With this resource bundle, the usage help for the git commit command looks like this:

Usage: git commit [--squash=<commit>] [-m=<arg0>] [<file>...] Record changes to the repository [<file>...] The files to commit. --squash=<commit> Construct a commit message for use with rebase --autosquash. -m, --message=<arg0> Use the given <msg> as the commit message.

§21.3.3. Mixin Support in @Command Methods

As of picocli 3.8, @Command methods accept @Mixin parameters. All options and positional parameters defined in the mixin class are added to the command.

Example:

Java
Kotlin
javaclass CommonParams {
    @Option(names = "-x") int x;
    @Option(names = "-y") int y;
}

@Command
class App {
    @Command
    public void doit(@Mixin CommonParams params, @Option(names = "-z") int z) {}
}

In the above example, the -x and -y options are added to the -z-option of the doit command.

§21.4. @Spec Annotation

Picocli 3.2 introduces a @Spec annotation for injecting the CommandSpec model of the command into a command field.

From picocli 4.6, @Spec-annotated elements can be used in ArgGroup classes.

This is useful when a command needs to use the picocli API, for example to walk the command hierarchy and iterate over its sibling commands. This complements the @ParentCommand annotation; the @ParentCommand annotation injects a user-defined command object, whereas this annotation injects a picocli class.

Java
Kotlin
java@Command
class InjectSpecExample implements Runnable {
    @Spec CommandSpec commandSpec;
    //...

    public void run() {
        // do something with the injected spec
        System.out.println("My full name is " + commandSpec.qualifiedName());
    }
}

§21.4.1. @Spec(MIXEE) Annotation

As of picocli 4.3, the @Spec annotation has a value element. The value is Spec.Target.SELF by default, meaning that the CommandSpec of the enclosing class is injected into the @Spec-annotated field.

For classes that are used as a mixin, there is another value that may be useful. When @Spec(Spec.Target.MIXEE) is specified in a mixin class, the CommandSpec of the command receiving this mixin (the "mixee") is injected into the @Spec-annotated field. This can be useful when a mixin contains logic that is common to many commands. See Accessing the Mixee from a Mixin for more details.

§21.5. Custom Factory

Declaratively registered subcommands, type converters and version providers must be instantiated somehow. As of picocli 2.2, a custom factory can be specified when constructing a CommandLine instance. This allows full control over object creation and opens possibilities for Inversion of Control and Dependency Injection (see next section). For example:

Java
Kotlin
javaIFactory myFactory = getCustomFactory();
CommandLine cmdLine = new CommandLine(new Git(), myFactory);

Custom factories need to implement the picocli.CommandLine.IFactory interface:

javapublic interface IFactory {
    /**
     * Creates and returns an instance of the specified class.
     * @param clazz the class to instantiate
     * @param <K> the type to instantiate
     * @return the new instance
     * @throws Exception an exception detailing what went wrong when creating the instance
     */
    <K> K create(Class<K> clazz) throws Exception;
}

If no factory is specified, a default factory is used. The default factory requires that the classes to instantiate have a public no-argument constructor: it instantiates the class by first calling clazz.newInstance(), and if that fails, clazz.getDeclaredConstructor().newInstance().

From version 4.0, picocli delegates all object creation to the factory, including creating Collection instances to capture multi-value @Option values. Previously, Collection objects were instantiated separately without involving the factory.

It is recommended that custom factories should fall back to the default factory. Something like this:

Java
Kotlin
java@Override
public <K> K create(Class<K> clazz) throws Exception {
    try {
        return doCreate(clazz); // custom factory lookup or instantiation
    } catch (Exception e) {
        return CommandLine.defaultFactory().create(clazz); // fallback if missing
    }
}

§21.6. Model Transformations

From picocli 4.6, it is possible to use the annotations API to modify the model (commands, options, subcommands, etc.) dynamically at runtime. The @Command annotation now has a modelTransformer attribute where applications can specify a class that implements the IModelTransformer interface:

javainterface IModelTransformer {
    /**
     * Given an original CommandSpec, return the object that should be used
     * instead. Implementors may modify the specified CommandSpec and return it,
     * or create a full or partial copy of the specified CommandSpec, and return
     * that, or even return a completely new CommandSpec.
     * <p>
     * Implementors are free to add or remove options, positional parameters,
     * subcommands or modify the command in any other way.
     * </p><p>
     * This method is called once, after the full command hierarchy is
     * constructed, and before any command line arguments are parsed.
     * </p>
     * @return the CommandSpec to use instead of the specified one
     */
    CommandSpec transform(CommandSpec commandSpec);
}

This allows applications to dynamically add or remove options, positional parameters or subcommands, or modify the command in any other way, based on some runtime condition.

Java
Kotlin
java@Command(modelTransformer = Dynamic.SubCmdFilter.class)
class Dynamic {

    static class SubCmdFilter implements IModelTransformer {
        public CommandSpec transform(CommandSpec commandSpec) {
            if (Boolean.getBoolean("disable_sub")) {
                commandSpec.removeSubcommand("sub");
            }
            return commandSpec;
        }
    }

    @Command
    private void sub() {
        // subcommand, business logic
    }
}

All transformers are called once, after the full command hierarchy is constructed, and before any command line arguments are parsed.

If your model transformer is declared as nested class, make sure you mark this class as static, or picocli will not be able to instantiate your transformer class.

§21.7. Automatic Parameter Indexes

§21.7.1. Automatic Indexes

Positional parameters have an index attribute that determines which command line argument(s) are captured. It is possible to omit the index attribute and let picocli automatically assign an index.

This means different things for single-value and for multi-value positional parameters.

For multi-value positional parameters (arrays or collections), omitting the index attribute means the field captures all positional parameters (the equivalent of index = "0..*").

For single-value positional parameters, picocli’s behaviour has changed since version 4.3: prior to picocli 4.3, the default index for single-value positional parameters was also index = "0..*", even though only one value (usually the first argument) can be captured. From version 4.3, the default index is index = "0+", which tells picocli to assign an index automatically, starting from zero, based on the other positional parameters defined in the same command. A simple example can look like this:

Java
Kotlin
javaclass AutomaticIndex {
    @Parameters(hidden = true)   // "hidden": don't show this parameter in usage help message
    List<String> allParameters;  // no "index" attribute: captures _all_ arguments

    @Parameters String group;    // assigned index = "0"
    @Parameters String artifact; // assigned index = "1"
    @Parameters String version;  // assigned index = "2"
}

Picocli initializes fields with the values at the specified index in the arguments array.

Java
Kotlin
javaString[] args = { "info.picocli", "picocli", "4.3.0" };
AutomaticIndex auto = CommandLine.populateCommand(new AutomaticIndex(), args);

assert auto.group.equals("info.picocli");
assert auto.artifact.equals("picocli");
assert auto.version.equals("4.3.0");
assert auto.allParameters.equals(Arrays.asList(args));

Automatic indexes depend on the ability of Java reflection and Java annotation processors to iterate over fields in declaration order in the source code. Officially this is not guaranteed by the Java spec. In practice this has worked in Oracle JVMs and OpenJDK from Java 6, but there is some risk this may not work in the future or on other JVMs. In general, for single-value positional parameters, using explicit indexes is the safer option. (Multi-value positional parameters can safely omit the index attribute.)

Methods cannot be iterated over in predictable order. For applications with @Parameters-annotated methods or combinations of @Parameters-annotated methods and @Parameters-annotated fields, we recommend using explicit indexes for single-value positional parameters.

§21.7.2. Anchored Automatic Index

The default automatic index (index = "0+") for single-value positional parameters is "anchored at zero": it starts at zero, and is increased with each additional positional parameter.

Sometimes you want to have indexes assigned automatically from a different starting point than zero. This can be useful when defining Mixins with positional parameters.

To accomplish this, specify an index with the anchor point and a + character to indicate that picocli should start to automatically assign indexes from that anchor point. For example:

Java
Kotlin
javaclass Anchored {
    @Parameters(index = "1+") String p1; // assigned index = "1" or higher
    @Parameters(index = "1+") String p2; // assigned index = "2" or higher
}

§21.7.3. Combining Explicit and Automatic Indexes

If a command defines a positional parameter with an explicit index at the anchor point, then automatic indexes anchored at that point will start from the explicit index plus 1. For example:

Java
Kotlin
javaclass ExplicitAndAutomaticIndexes {
    @Parameters(index = "0" ) String explicit0;  // explicit index "0" at default anchor point
    @Parameters String pAuto1;                   // assigned index "1", anchor point + 1
    @Parameters String pAuto2;                   // assigned index "2"
    @Parameters(index = "1+") String pAnchored1; // assigned index "1": no explicit index 1
    @Parameters(index = "1+") String pAnchored2; // assigned index "2"
    @Parameters(index = "2" ) String explicit2;  // explicit index "2", matching anchor for "2+"
    @Parameters(index = "2+") String pAnchored3; // assigned index "3", anchor point + 1
    @Parameters(index = "2+") String pAnchored4; // assigned index "4"
}

§21.7.4. Unanchored Automatic Index

Sometimes you want to have indexes assigned automatically to come at the end. This can be useful when defining Mixins with positional parameters.

To accomplish this, specify an index with a + character to indicate that picocli should automatically assign indexes that come at the end. For example:

Java
Kotlin
javaclass Unanchored {
    @Parameters(index = "0" ) String p0;
    @Parameters(index = "1+") String p1; // assigned index = "1"
    @Parameters(index = "1+") String p2; // assigned index = "2"
    @Parameters(index = "3" ) String p3;
    @Parameters(index = "+" ) String p4; // assigned index = "4" <-- unanchored
    @Parameters(index = "+" ) String p5; // assigned index = "5" <-- unanchored
}

§21.7.5. Do Not Combine Unanchored Indexes with Open-ended Indexes

It is not possible to combine unanchored indexes (+) with open-ended explicit indexes (*): the parameter with the open-ended index will capture all arguments, and there is no position "after the other indexes" that can be assigned.

The below example will always give a Missing required parameter: <last> error:

Java
Kotlin
javaclass BadCombination {
    @Parameters(index = "0..*") List<String> all;
    @Parameters(index = "+"   ) String last;
}

§21.8. Improved Support for Chinese, Japanese and Korean

Picocli will align the usage help message to fit within some user-defined width (80 columns by default). A number of characters in Chinese, Japanese and Korean (CJK) are wider than others. If those characters are treated to have the same width as other characters, the usage help message may extend past the right margin.

From 4.0, picocli will use 2 columns for these wide characters when calculating where to put line breaks, resulting in better usage help message text.

This can be switched off with CommandLine.setAdjustLineBreaksForWideCJKCharacters(false).

§21.9. Boolean Options with Parameters

By default the value of a boolean field is set to the logical negative of its default value when the option is specified on the command line.

It is possible to let end users explicitly specify "true" or "false" as a parameter for a boolean option by defining an explicit Arity attribute. A boolean option with arity = "0..1" accepts zero to one parameters, arity = "1" means the option must have one parameter. For example:

Java
Kotlin
javaclass BooleanOptionWithParameters {
    @Option(names = "-x", arity = "1", description = "1 mandatory parameter")
    boolean x;

    @Option(names = "-y", arity = "0..1", description = "min 0 and max 1 parameter")
    boolean y;
}

The following ways to invoke the program will be accepted (values are not case sensitive):

<command> -x true <command> -x FALSE <command> -x TRUE -y <command> -x True -y False

But trying to specify the -x option without a parameter, or with a value other than "true" or "false" (case insensitive) will result in a ParameterException.

§21.10. Hexadecimal Values

Numeric values are interpreted as decimal numbers by default. If you want picocli to be more flexible, you can register a custom type converter that delegates to the decodehttps://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#decode-java.lang.String- method to convert strings to numbers.

The decode method looks at the prefix to determine the radix, so numbers starting with 0x, 0X or # are interpreted as hexadecimal numbers, numbers starting with 0 are interpreted as octal numbers, and otherwise the number is interpreted as a decimal number.
Java 8
Kotlin
javanew CommandLine(obj)
        .registerConverter(Byte.class,    Byte::decode)
        .registerConverter(Byte.TYPE,     Byte::decode)
        .registerConverter(Short.class,   Short::decode)
        .registerConverter(Short.TYPE,    Short::decode)
        .registerConverter(Integer.class, Integer::decode)
        .registerConverter(Integer.TYPE,  Integer::decode)
        .registerConverter(Long.class,    Long::decode)
        .registerConverter(Long.TYPE,     Long::decode);
Java 5
Kotlin
javaITypeConverter<Integer> intConverter = new ITypeConverter<Integer>() {
    public Integer convert(String s) {
        return Integer.decode(s);
    }
};
commandLine.registerConverter(Integer.class, intConverter);
commandLine.registerConverter(Integer.TYPE,  intConverter);
// ...

§21.11. Option-Parameter Separators

§21.11.1. Default Separators

Options may take an option parameter (also called option-argument). For POSIX-style short options (like -f or -c), the option parameter may be attached to the option, or it may be separated by a space or the separator string (= by default). That is, all of the below are equivalent:

bash<command> -foutput.txt
<command> -f output.txt
<command> -f=output.txt

Long option names (like --file) must be separated from their option parameter by a space or the separator string (= by default). That is, the first two below examples are valid but the last example is invalid:

bash// valid (separator between --file and its parameter)
<command> --file output.txt
<command> --file=output.txt

// invalid (picocli will not recognize the --file option when attached to its parameter)
<command> --fileoutput.txt

§21.11.2. Custom Separators

The separator string can be customized programmatically or declaratively.

Use the separator attribute of the @Command annotation to declaratively set a separator string:

Java
Kotlin
java@Command(separator = ":")  // declaratively set a separator
class OptionArg {
    @Option(names = { "-f", "--file" }) String file;
}
Java
Kotlin
javaOptionArg optionArg = CommandLine.populateCommand(new OptionArg(), "-f:output.txt");
assert optionArg.file.equals("output.txt");

Alternatively, the separator string can be changed programmatically with the CommandLine.setSeparator(String separator) method. For example:

Java
Kotlin
javaOptionArg optionArg     = new OptionArg();
CommandLine commandLine = new CommandLine(optionArg);

commandLine.setSeparator(":"); // programmatically set a separator
commandLine.parseArgs("-f:output.txt");
assert optionArg.file.equals("output.txt");

§21.12. Best Practices for Command Line Interfaces

Picocli makes it easy to follow this new and modern set of Command Line Interface Guidelineshttps://clig.dev/#guidelines.

Generally, many applications use options for optional values and parameters for mandatory values. However, picocli lets you make options required if you want to, see Required Arguments.

§21.13. Text Blocks for Java 15

When writing your command line program, you can use Java 15’s new "Text Block" feature. Multi-line text blocks can be used in command and option descriptions, headers and footers.

Java 15
Kotlin
java@Option(names = { "-v", "--verbose" },
  description = """
                Verbose mode. Helpful for troubleshooting.
                Multiple -v options increase the verbosity.
                """) // write description in text block

For more details, see this articlehttps://www.infoq.com/articles/java-text-blocks/ by Java Language Architect Brian Goetz.

§22. Dependency Injection

§22.1. Guice Example

Image (Asset 4/17) alt= PicocliBaseScript2 PicocliBaseScript

Subcommands as methods

Supported, subcommands can be defined as @Command-annotated methods in the script.

Unsupported

help subcommands

Supported, both the built-in one and custom ones.

Unsupported

Exit codes

Supported: scripts can override afterExecution(CommandLine, int, Exception) to call System.exit.

Unsupported

Command execution

Via invocation of CommandLine::executehttps://picocli.info/#execute. Scripts can override beforeParseArgs(CommandLine) to install a custom IExecutionStrategy.

Execution after parsing is defined in PicocliBaseScript::run and is not easy to customize. Any subcommand and the main script are both executed.

Custom handling of invalid user input

Scripts can override beforeParseArgs(CommandLine) to install a custom IParameterExceptionHandler.

Invalid input handling can be customized by overriding PicocliBaseScript::handleParameterException.

Custom error handling

Scripts can override beforeParseArgs(CommandLine) to install a custom IExecutionExceptionHandler.

Runtime exception handling can be customized by overriding PicocliBaseScript::handleExecutionException.

Principle

Implements Callable<Object>, script body is transformed to the call method.

Script body is transformed to the runScriptBody method.

When upgrading scripts from picocli versions older than 4.0, just changing the version number is not enough! Scripts should use @Grab('info.picocli:picocli-groovy:4.7.1'). The old artifact id @Grab('info.picocli:picocli:4.7.1') will not work, because the @picocli.groovy.PicocliScript annotation class and supporting classes have been moved into a separate module, picocli-groovy.

§30.1.3. Closures in Annotations

As of picocli 4.6, Groovy programs can use closures in the picocli annotations instead of specifying a class. This can be especially useful in Groovy scripts, where one cannot define a static inner class.

Example:

groovy@Command(name = "ClosureDemo",
        versionProvider = {
            { -> ["line1" , "line2"] as String[] } as IVersionProvider 1
        },
        defaultValueProvider = {
            { argSpec -> "some default" } as IDefaultValueProvider 2
        })
class ClosureDemo {
    @Option(names = '-x', completionCandidates = {["A", "B", "C"]}) 3
    String x

    @Option(names = '-y',
            parameterConsumer = {
                { args, argSpec, commandSpec ->  4
                    argSpec.setValue(args.toString() + commandSpec.name())
                    args.clear()
                } as IParameterConsumer
            })
    String y

    @Option(names = '-z', converter = [ // requires Groovy 3.0.7
            { { str -> MessageDigest.getInstance(str) } as ITypeConverter } 5
    ])
    MessageDigest z
}

When a class is specified, picocli creates an instance of the class. By contrast, when a closure is specified, picocli calls the closure to get an instance. (To be precise, both of these are delegated to the configured factory, and the default factory implementation supports closures from picocli 4.6.)

As you can see in the above example, each closure in the annotation should contain another closure that has the required type (IVersionProvider, IDefaultValueProvider, etc.)

1 Command versionProvider: note the empty parameter list before the arrow. This is needed to help the Groovy compiler. The closure must be cast to IVersionProvider.
2 Command defaultProvider: return a default value for the specified ArgSpec parameter. The closure must be cast to IDefaultValueProvider.
3 Option or Parameters completionCandidates: return a list of Strings. No parameter list or casting is required.
4 Option or Parameters parameterConsumer: given a Stack, ArgSpec and CommandSpec, process the remaining arguments. The closure must be cast to IParameterConsumer.
5 Option or Parameters type converter takes an array of closures. Groovy 3.0.7 or greater is required: older versions of Groovy ignore closures in class array annotations. Each closure must have a parameter and be cast to ITypeConverter.
From picocli version 4.7.0, this feature can be disabled by setting system property picocli.disable.closures to true. Disabling support for closures in annotations can improve the application startup time.

§30.1.4. Older Versions of Groovy

When using a Groovy version older than 2.4.7, use this workaround for the Grape bughttps://issues.apache.org/jira/browse/GROOVY-7613 that causes this error: java.lang.ClassNotFoundException: # Licensed to the Apache Software Foundation (ASF) under one or more.
groovy@Grab('info.picocli:picocli-groovy:4.7.1')
@GrabExclude('org.codehaus.groovy:groovy-all') // work around GROOVY-7613
...

In order to get you started quickly, you may have a look at the Groovy exampleshttps://github.com/remkop/picocli/tree/main/picocli-examples/src/main/groovy/picocli/examples folder of the picocli code repository.

§30.2. Kotlin

§30.2.1. Kotlin Annotation Syntax

Kotlin 1.2 (released Nov 28, 2017) officially supports array literals in annotationshttps://kotlinlang.org/docs/reference/whatsnew12.html#array-literals-in-annotations, allowing a more compact notation:

kotlin@Command(name = "MyApp", version = ["Kotlin picocli v4.0 demo"],
        mixinStandardHelpOptions = true, // add --help and --version options
        description = ["@|bold Kotlin|@ @|underline picocli|@ example"])
class MyApp : Callable<Int> {

    @Option(names = ["-c", "--count"], paramLabel = "COUNT",
            description = ["the count"])
    private var count: Int = 1

    override fun call(): Int {
        for (i in 0 until count) {
            println("hello world $i...")
        }
        return 0
    }
}
fun main(args: Array<String>) = System.exit(CommandLine(MyApp()).execute(*args))

When specify a class as an argument of an annotation, use a Kotlin class (KClasshttps://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/index.html). The Kotlin compiler will automatically convert it to a Java class, so that the Java code will be able to see the annotations and arguments normally.

kotlin@Command(name = "top", // ...
        subcommands = [SubCmd::class, picocli.CommandLine.HelpCommand::class])
class TopCmd { // ...
}

@Command(name = "sub", /* ... */)
class SubCmd { // ...
}

§30.2.2. Older Versions of Kotlin

Kotlin versions prior to 1.2 did not allow the array literal syntax in annotationshttps://kotlinlang.org/docs/reference/annotations.html, so with older versions of Kotlin you will have to write arrayOf(…​) for the names, description and type attributes.

kotlin@Command(name = "MyApp", version = arrayOf("picocli demo for Kotlin v1.0 and Kotlin v1.1"),
        mixinStandardHelpOptions = true, // add --help and --version options
        description = arrayOf("@|bold Kotlin|@ @|underline picocli|@ example"))
class MyApp : Callable<Int> {

    @Option(names = arrayOf("-c", "--count"),
            description = arrayOf("number of repetitions"))
    private var count: Int = 1

    override fun call(): Int {
        for (i in 0 until count) {
            println("hello world $i...")
        }
        return 0
    }
}
fun main(args: Array<String>) = System.exit(CommandLine(MyApp()).execute(*args))

In order to get you started quickly, you may have a look at the Kotlin exampleshttps://github.com/remkop/picocli/tree/main/picocli-examples/src/main/kotlin/picocli/examples/kotlin folder of the picocli code repository.

§30.3. Scala

Scala does not allow specifying array annotation attribute as a single value, so be aware that you will have to write Array(…​) for the names, description and type attributes.

scala@Command(name = "MyApp", version = Array("Scala picocli demo v4.0"),
         mixinStandardHelpOptions = true, // add --help and --version options
         description = Array("@|bold Scala|@ @|underline picocli|@ example"))
class MyApp extends Callable[Int] {

    @Option(names = Array("-c", "--count"), paramLabel = "COUNT",
            description = Array("the count"))
    private var count: Int = 1

    def call() : Int = {
        for (i <- 0 until count) {
            println(s"hello world $i...")
        }
        0
    }
}
object MyApp {
    def main(args: Array[String]): Unit = {
        System.exit(new CommandLine(new MyApp()).execute(args: _*))
    }
}

In order to get you started quickly, you may have a look at the Scala exampleshttps://github.com/remkop/picocli/tree/main/picocli-examples/src/main/scala/picocli/examples/scala folder of the picocli code repository.

§31. API JavaDoc

Picocli API JavaDoc can be found here.

§32. GitHub Project

The GitHub projecthttps://github.com/remkop/picocli has the source code, tests, build scripts, etc.

Star and/or fork this project on GitHub if you like it!

§33. Issue Tracker

The GitHub Issue Trackerhttps://github.com/remkop/picocli/issues can be used to report bugs or request features. There is also a Mailing List, and for questions where the user community may know the answer, StackOverflowhttps://stackoverflow.com/questions/tagged/picocli is both a good resource and a great way to build an online knowledge base.

§34. Mailing List

Join the picocli Google grouphttps://groups.google.com/d/forum/picocli if you are interested in discussing anything picocli-related and receiving announcements on new releases.

§36. Releases

Previous versions are available from the GitHub project Releaseshttps://github.com/remkop/picocli/releases.

§37. Download

You can add picocli as an external dependency to your project, or you can include it as source.

§37.1. Build tools

Gradle
Gradle (Kotlin)
Maven
Scala SBT
Ivy
Grape
Leiningen
Buildr
JBang
groovyimplementation 'info.picocli:picocli:4.7.1'

§37.2. Source

By using picocli in source form, you can avoid having an external dependency on picocli. Picocli has only one source file: CommandLine.javahttps://github.com/remkop/picocli/blob/v4.7.1/src/main/java/picocli/CommandLine.java. This facilitates including picocli in your project: simply copy and paste the code of this file into a file called CommandLine.java, add it to your project, and enjoy!

Version 4.7.1
Last updated 2023-01-27 18:22:36 +0900