Fork me on GitHub
Every main method deserves picocli!

picocli the Mighty Tiny Command Line Interface

The user manual for the latest release is at https://picocli.info. For the busy and impatient: there is also a Quick Guide.

1. Introduction

Picocli aims to be the easiest way to create rich command line applications that can run on and off the JVM. Considering picocli? Check what happy users say about picocli.

1.1. Overview

Picocli is a one-file framework for creating Java command line applications with almost zero code. It supports a variety of command line syntax styles including POSIX, GNU, MS-DOS and more. It generates highly customizable usage help messages that use ANSI colors and styles to contrast important elements and reduce the cognitive load on the user.

Picocli-based applications can have command line TAB completion showing available options, option parameters and subcommands, for any level of nested subcommands. Picocli-based applications can be ahead-of-time compiled to a GraalVM native image, with extremely fast startup time and lower memory requirements, which can be distributed as a single executable file.

Picocli generates beautiful documentation for your application (HTML, PDF and Unix man pages).

An example usage help message with ANSI colors and styles

Screenshot of usage help with Ansi codes enabled

Another distinguishing feature of picocli is how it aims to let users run picocli-based applications without requiring picocli as an external dependency: all the source code lives in a single file, to encourage application authors to include it in source form.

Picocli makes it easy to follow Command Line Interface Guidelines.

How it works: annotate your class and picocli initializes it from the command line arguments, converting the input to strongly typed values in the fields of your class.

Picocli also provides a programmatic API, separately from the annotations API.

1.2. Example application

The example below shows a short but fully functional picocli-based checksum command line application.

Java
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

import java.io.File;
import java.math.BigInteger;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.concurrent.Callable;

@Command(name = "checksum", mixinStandardHelpOptions = true, version = "checksum 4.0",
         description = "Prints the checksum (SHA-256 by default) of a file to STDOUT.")
class CheckSum implements Callable<Integer> {

    @Parameters(index = "0", description = "The file whose checksum to calculate.")
    private File file;

    @Option(names = {"-a", "--algorithm"}, description = "MD5, SHA-1, SHA-256, ...")
    private String algorithm = "SHA-256";

    @Override
    public Integer call() throws Exception { // your business logic goes here...
        byte[] fileContents = Files.readAllBytes(file.toPath());
        byte[] digest = MessageDigest.getInstance(algorithm).digest(fileContents);
        System.out.printf("%0" + (digest.length*2) + "x%n", new BigInteger(1, digest));
        return 0;
    }

    // this example implements Callable, so parsing, error handling and handling user
    // requests for usage help or version help can be done with one line of code.
    public static void main(String... args) {
        int exitCode = new CommandLine(new CheckSum()).execute(args);
        System.exit(exitCode);
    }
}
Groovy
@Grab('info.picocli:picocli-groovy:4.7.6')
import picocli.CommandLine
import static picocli.CommandLine.*

import java.security.MessageDigest
import java.util.concurrent.Callable

@Command(name = 'checksum', mixinStandardHelpOptions = true, version = 'checksum 4.0',
  description = 'Prints the checksum (SHA-256 by default) of a file to STDOUT.')
class Checksum implements Callable<Integer> {

    @Parameters(index = '0', description = 'The file whose checksum to calculate.')
    File file

    @Option(names = ['-a', '--algorithm'], description = 'MD5, SHA-1, SHA-256, ...')
    String algorithm = 'SHA-256'

    Integer call() throws Exception {
        println MessageDigest.getInstance(algorithm).digest(file.bytes).encodeHex().toString()
        0
    }

    static void main(String[] args) {
        System.exit(new CommandLine(new Checksum()).execute(args))
    }
}
Groovy script
@Grab('info.picocli:picocli-groovy:4.7.6')
import static picocli.CommandLine.*
import groovy.transform.Field
import java.security.MessageDigest

@Command(name = 'checksum', mixinStandardHelpOptions = true, version = 'checksum 4.0',
  description = 'Prints the checksum (SHA-256 by default) of a file to STDOUT.')
@picocli.groovy.PicocliScript

@Parameters(index = '0', description = 'The file whose checksum to calculate.')
@Field File file

@Option(names = ['-a', '--algorithm'], description = 'MD5, SHA-1, SHA-256, ...')
@Field String algorithm = 'SHA-256'

println MessageDigest.getInstance(algorithm).digest(file.bytes).encodeHex().toString()
Kotlin
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.Option
import picocli.CommandLine.Parameters

import java.io.File
import java.math.BigInteger
import java.nio.file.Files
import java.security.MessageDigest
import java.util.concurrent.Callable
import kotlin.system.exitProcess

@Command(name = "checksum", mixinStandardHelpOptions = true, version = ["checksum 4.0"],
    description = ["Prints the checksum (SHA-256 by default) of a file to STDOUT."])
class Checksum : Callable<Int> {

    @Parameters(index = "0", description = ["The file whose checksum to calculate."])
    lateinit var file: File

    @Option(names = ["-a", "--algorithm"], description = ["MD5, SHA-1, SHA-256, ..."])
    var algorithm = "SHA-256"

    override fun call(): Int {
        val fileContents = Files.readAllBytes(file.toPath())
        val digest = MessageDigest.getInstance(algorithm).digest(fileContents)
        println(("%0" + digest.size * 2 + "x").format(BigInteger(1, digest)))
        return 0
    }
}

fun main(args: Array<String>) : Unit = exitProcess(CommandLine(Checksum()).execute(*args))
Scala
import picocli.CommandLine
import picocli.CommandLine.{Command, Option, Parameters}

import java.io.File
import java.math.BigInteger
import java.nio.file.Files
import java.security.MessageDigest
import java.util.concurrent.Callable

@Command(name = "checksum", mixinStandardHelpOptions = true, version = Array("checksum 4.0"),
  description = Array("Prints the checksum (SHA-256 by default) of a file to STDOUT."))
class Checksum extends Callable[Int] {

  @Parameters(index = "0", description = Array("The file whose checksum to calculate."))
   private var file: File = null

  @Option(names = Array("-a", "--algorithm"), description = Array("MD5, SHA-1, SHA-256, ..."))
  private var algorithm = "SHA-256"

  def call(): Int = {
    val fileContents = Files.readAllBytes(file.toPath)
    val digest = MessageDigest.getInstance(algorithm).digest(fileContents)
    println(("%0" + digest.size * 2 + "x").format(new BigInteger(1, digest)))
    0
  }
}

object Checksum {
  def main(args: Array[String]): Unit = {
    System.exit(new CommandLine(new Checksum()).execute(args: _*))
  }
}

You can run this example online. Try it without arguments, with an option like --help or --version, or with a file name like /usr/bin/java as command line argument.

Implement Runnable or Callable and your command can be executed in one line of code. The example main method calls CommandLine.execute to parse the command line, handle errors, handle requests for usage and version help, and invoke the business logic. Applications can call System.exit with the returned exit code to signal success or failure to their caller.

The mixinStandardHelpOptions attribute adds --help and --version options to your application.

The picocli Quick Guide shows other sample applications and explains them in more detail.

The picocli-examples module in the picocli git repository has many more examples.

2. Getting Started

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

2.1. Add as External Dependency

Below are examples of configuring Gradle or Maven to use picocli as an external dependency in your project:

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

2.2. Add as Source

To include as source, get the source code from the GitHub file. Copy and paste it into a file called CommandLine.java, add it to your project, and enjoy!

2.3. Annotation Processor

The picocli-codegen module includes an annotation processor that can build a model from the picocli annotations at compile time rather than at runtime.

Enabling this annotation processor in your project is optional, but strongly recommended. Use this if you’re interested in:

  • Compile time error checking. The annotation processor shows errors for invalid annotations and attributes immediately when you compile, instead of during testing at runtime, resulting in shorter feedback cycles.

  • GraalVM native images. The annotation processor generates and updates GraalVM configuration files under META-INF/native-image/picocli-generated/$project during compilation, to be included in the application jar. This includes configuration files for reflection, resources and dynamic proxies. By embedding these configuration files, your jar is instantly Graal-enabled. In most cases no further configuration is needed when generating a native image.

2.3.1. Processor option: project

The picocli annotation processor supports a number of options, most important of which is the project option to control the output subdirectory: the generated files are written to META-INF/native-image/picocli-generated/${project}. A good convention is to use the Maven ${project.groupId}/${project.artifactId} as the value; a unique subdirectory ensures your jar can be shaded with other jars that may also contain generated configuration files.

To configure this option, pass the -Aproject=<some value> to the javac compiler. The examples below show how to do this for Maven and Gradle.

2.3.2. Enabling the Annotation Processor

IDE

This page shows the steps to configure Eclipse and IntelliJ IDEA to enable annotation processing.

Using Build Tools
Gradle
dependencies {
    implementation 'info.picocli:picocli:4.7.6'
    annotationProcessor 'info.picocli:picocli-codegen:4.7.6'
}


compileJava {
    options.compilerArgs += ["-Aproject=${project.group}/${project.name}"]
}
Maven
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <!-- annotationProcessorPaths requires maven-compiler-plugin version 3.5 or higher -->
  <version>${maven-compiler-plugin-version}</version>
  <configuration>
    <annotationProcessorPaths>
      <path>
        <groupId>info.picocli</groupId>
        <artifactId>picocli-codegen</artifactId>
        <version>4.7.6</version>
      </path>
    </annotationProcessorPaths>
    <compilerArgs>
      <arg>-Aproject=${project.groupId}/${project.artifactId}</arg>
    </compilerArgs>
  </configuration>
</plugin>

See the picocli-codegen README for more details.

Kotlin Projects Using Gradle

Kotlin projects should add the kotlin-kapt plugin to enable the Kotlin Annotation processing tool (kapt), then replace annotationProcessor with kapt:

apply plugin: 'kotlin-kapt' // required
dependencies {
    // ...
    kapt 'info.picocli:picocli-codegen:4.7.6'
}

And replace compileJava.options.compilerArgs with kapt.arguments:

kapt {
    arguments {
        arg("project", "${project.group}/${project.name}")
    }
}

See the picocli-codegen README for more details.

2.4. Running the Application

After we successfully compiled our example CheckSum application, let’s quickly look at how to run it.

There are many ways to run picocli-based applications, depending on whether we included picocli as source, created a jar for our application or not, and whether we created a shaded jar (also known as uber-jar) containing all dependencies.

Before we run our CheckSum application, let’s create an example file whose checksum we want to print. For example:

echo "hello" > hello.txt

Now, assuming we created a jar named checksum.jar containing our compiled CheckSum.class, we can run the application with java -cp <classpath> <MainClass> [OPTIONS]. For example:

java -cp "picocli-4.7.6.jar:checksum.jar" CheckSum --algorithm SHA-1 hello.txt

You may want to package your application in such a way that end users can invoke it with a short command like this:

checksum --algorithm SHA-1 hello.txt

See the Packaging Your Application section for ideas on how to accomplish this.

3. Options and Parameters

Command line arguments can be separated into options and positional parameters. Options have a name, positional parameters are usually the values that follow the options, but they may be mixed.

Example command with annotated @Option and @Parameters

Picocli has separate annotations for options and positional parameters.

3.1. Options

An option must have one or more names. Picocli lets you use any option name you want. Option names are case-sensitive by default, but this is customizable.

You may be interested in this list of common option names. Following these conventions may make your application more intuitive to use for experienced users.

The below example shows options with one or more names, options that take an option parameter, and a help option.

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

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

    @Parameters(paramLabel = "FILE", description = "one or more files to archive")
    File[] files;

    @Option(names = { "-h", "--help" }, usageHelp = true, description = "display a help message")
    private boolean helpRequested = false;
}
Kotlin
class Tar : Runnable {
    @Option(names = ["-c"], description = ["create a new archive"])
    var create: Boolean = false;

    @Option(names = ["-f", "--file"], paramLabel = "ARCHIVE", description = ["the archive file"])
    lateinit var archive: File;

    @Parameters(paramLabel = "FILE", description = ["one or more files to archive"])
    lateinit var files: Array<File>;

    @Option(names = ["-h", "--help"], usageHelp = true, description = ["display a help message"])
    private var helpRequested: Boolean = false;
}

Picocli matches the option names to set the field values.

Java
String[] args = { "-c", "--file", "result.tar", "file1.txt", "file2.txt" };
Tar tar = new Tar();
new CommandLine(tar).parseArgs(args);

assert !tar.helpRequested;
assert  tar.create;
assert  tar.archive.equals(new File("result.tar"));
assert  Arrays.equals(tar.files, new File[] {new File("file1.txt"), new File("file2.txt")});
Kotlin
val args1 = arrayOf("-c", "--file", "result.tar", "file1.txt", "file2.txt")
var tar = Tar()
CommandLine(tar).parseArgs(*args1);

assert(!tar.helpRequested)
assert(tar.create)
assert(tar.archive.equals(File("result.tar")))
assert(Arrays.equals(tar.files, arrayOf<File>(File("file1.txt"), File("file2.txt"))))

3.2. Interactive (Password) Options

Picocli 3.5 introduced password support: for options and positional parameters marked as interactive, the user is prompted to enter a value on the console. When running on Java 6 or higher, picocli will use the Console.readPassword API so that user input is not echoed to the console.

From picocli 4.6, applications can choose to echo user input to the console by setting echo = true, and set the prompt text to control what is shown on the console when asking the user for input.

Interactive positional parameters have a limitation: they must be followed by a non-interactive positional parameter. Commands where the last positional parameter is interactive are currently not supported.

3.2.1. Example

The example below demonstrates how an interactive option can be used to specify a password. From picocli 3.9.6, interactive options can use type char[] instead of String, to allow applications to null out the array after use so that sensitive information is no longer resident in memory.

Example usage:

Java
class Login implements Callable<Integer> {
    @Option(names = {"-u", "--user"}, description = "User name")
    String user;

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

    public Integer call() throws Exception {
        byte[] bytes = new byte[password.length];
        for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) password[i]; }

        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(bytes);

        System.out.printf("Hi %s, your password is hashed to %s.%n", user, base64(md.digest()));

        // null out the arrays when done
        Arrays.fill(bytes, (byte) 0);
        Arrays.fill(password, ' ');

        return 0;
    }

    private String base64(byte[] arr) { /* ... */ }
}
Kotlin
class Login : Callable<Int> {
    @Option(names = ["-u", "--user"], description = ["User name"])
    var user: String? = null

    @Option(names = ["-p", "--password"], description = ["Passphrase"], interactive = true)
    lateinit var password: CharArray

    override fun call(): Int {
        val bytes = ByteArray(password.size)
        for (i in bytes.indices) { bytes[i] = password[i].toByte() }

        val md = MessageDigest.getInstance("SHA-256")
        md.update(bytes)

        println(("Hi %s, your password is hashed to %s.").format(user, base64(md.digest())))

        // null out the arrays when done
        Arrays.fill(bytes, 0.toByte())
        Arrays.fill(password, ' ')

        return 0
    }

    private fun base64(arr: ByteArray): String { /* ... */ }
}

When this command is invoked like this:

Java
new CommandLine(new Login()).execute("-u", "user123", "-p");
Kotlin
CommandLine(Login()).execute("-u", "user123", "-p")

Then the user will be prompted to enter a value:

Enter value for --password (Passphrase):

When running on Java 6 or higher, the user input is not echoed to the console. After the user enters a password value and presses enter, the call() method is invoked, which prints something like the following:

Hi user123, your passphrase is hashed to 75K3eLr+dx6JJFuJ7LwIpEpOFmwGZZkRiB84PURz6U8=.

3.2.2. Optionally Interactive

Interactive options by default cause the application to wait for input on stdin. For commands that need to be run interactively as well as in batch mode, it is useful if the option can optionally consume an argument from the command line.

The default arity for interactive options is zero, meaning that the option takes no parameters. From picocli 3.9.6, interactive options can also take a value from the command line if configured with arity = "0..1". (See Optional Values.)

For example, if an application has these options:

Java
@Option(names = "--user")
String user;

@Option(names = "--password", arity = "0..1", interactive = true)
char[] password;
Kotlin
@Option(names = ["--user"])
lateinit var user: String

@Option(names = ["--password"], arity = "0..1", interactive = true)
lateinit var password: CharArray

With the following input, the password field will be initialized to "123" without prompting the user for input:

--password 123 --user Joe

However, if the password is not specified, the user will be prompted to enter a value. In the following example, the password option has no parameter, so the user will be prompted to type in a value on the console:

--password --user Joe
Providing Passwords to Batch Scripts Securely

Note that specifying a password in plain text on the command line or in scripts is not secure. There are alternatives that are more secure.

One idea is to add a separate different option (that could be named --password:file) that takes a File or Path parameter, where the application reads the password from the specified file. Another idea is to add a separate different option (that could be named --password:env) that takes an environment variable name parameter, where the application gets the password from the user’s environment variables.

A command that combines either of these with an interactive --password option (with the default arity = "0") allows end users to provide a password without specifying it in plain text on the command line. Such a command can be executed both interactively and in batch mode.

The picocli-examples module has an example, coded both in Java and Kotlin.

Interactive options and shell applications with JLine 2

Interactive options do not work in conjunction with JLine 2’s ConsoleReader. Either implement a IParameterConsumer which uses JLine2’s ConsoleReader directly or use picocli-shell-jline3.

3.2.3. Forcing Interactive Input

Be aware that picocli only prompts the user when the interactive option is specified without parameter:

$ myprogram                             # option not specified: no prompting
You provided value 'null'

$ myprogram --interactive-option=abc    # option specified with parameter: no prompting
You provided value 'abc'

$ myprogram --interactive-option        # option specified WITHOUT parameter: prompt for input
Enter value for --interactive-option (...):    #  <--- type xxx and hit Enter
You provided value 'xxx'

Applications that also need the user to be prompted when the option is not specified, need to do this in the business logic. For example:

Java
@Command
public class Main implements Runnable {
    @Option(names = "--interactive", interactive = true)
    String value;

    public void run() {
        if (value == null && System.console() != null) {
            // alternatively, use Console::readPassword
            value = System.console().readLine("Enter value for --interactive: ");
        }
        System.out.println("You provided value '" + value + "'");
    }

    public static void main(String[] args) {
        new CommandLine(new Main()).execute(args);
    }
}
Kotlin
@Command
class Main : Runnable {
    @Option(names = ["--interactive"], description = ["unattended run"], interactive = true)
    var value: String? = null
    override fun run() {
        if (value == null && System.console() != null) {
            // alternatively, use console::readPassword
            value = System.console().readLine("Enter value for --interactive: ")
        }
        println("You provided value '$value'")
    }
}

fun main(args: Array<String>) : Unit = exitProcess(CommandLine(Main()).execute(*args))

3.3. Short (POSIX) Options

Picocli supports POSIX clustered short options: one or more single-character options without option-arguments, followed by at most one option with an option-argument, can be grouped behind one '-' delimiter.

For example, given this annotated class:

Java
class ClusteredShortOptions {
    @Option(names = "-a") boolean aaa;
    @Option(names = "-b") boolean bbb;
    @Option(names = "-c") boolean ccc;
    @Option(names = "-f") String  file;
}
Kotlin
class ClusteredShortOptions {
    @Option(names = ["-a"]) var aaa = false
    @Option(names = ["-b"]) var bbb = false
    @Option(names = ["-c"]) var ccc = false
    @Option(names = ["-f"]) lateinit var file: String
}

The following command line arguments are all equivalent and parsing them will give the same result:

<command> -abcfInputFile.txt
<command> -abcf=InputFile.txt
<command> -abc -f=InputFile.txt
<command> -ab -cf=InputFile.txt
<command> -a -b -c -fInputFile.txt
<command> -a -b -c -f InputFile.txt
<command> -a -b -c -f=InputFile.txt
...
POSIX short options and usability

Applications can give a subtle hint to end users that an option is common and encouraged by providing both a short and a long name for an option. Conversely, the absence of a short option can signal that the option is unusual or perhaps should be used with care.

3.4. Boolean Options

Boolean options usually don’t need a parameter: it is enough to specify the option name on the command line.

Java
class BooleanOptions {
    @Option(names = "-x") boolean x;
}
Kotlin
class BooleanOptions {
    @Option(names = ["-x"]) var x = false
}

The value of x is false by default, and is set to true (the opposite of the default) if the -x option is specified on the command line. If the -x option is specified multiple times on the command line, the value of x remains true. (Prior to picocli 4.0, the value of x would "toggle" (flip to its opposite) for every -x option on the command line. This can still be configured if required.)

This is enough in most cases, but picocli offers alternatives for applications that need to get the value from something other than the default value. When the option is specified on the command line, the annotated field (or method) is assigned a value, as follows:

  • If the parser is configured to toggle boolean options, the opposite of the current value is assigned. (This was the default prior to picocli 4.0.)

  • If a fallback value is defined, the fallback value is assigned.

  • If the option is defined with a non-zero arity, and an option parameter was specified on the command line, this option parameter value is assigned.

  • Otherwise, the value assigned is the logical opposite of the default value.

3.5. Negatable Options

From picocli 4.0, boolean options can be negatable.

Java
@Command(name = "negatable-options-demo")
class NegatableOptionsDemo {
    @Option(names = "--verbose",           negatable = true) boolean verbose;
    @Option(names = "-XX:+PrintGCDetails", negatable = true) boolean printGCDetails;
    @Option(names = "-XX:-UseG1GC",        negatable = true) boolean useG1GC = true;
}
Kotlin
@Command(name = "negatable-options-demo")
class NegatableOptionsDemo {
    @Option(names = ["--verbose"],           negatable = true) var verbose = false
    @Option(names = ["-XX:+PrintGCDetails"], negatable = true) var printGCDetails = false
    @Option(names = ["-XX:-UseG1GC"],        negatable = true) var useG1GC = true
}

When an option is negatable, picocli will recognize negative aliases of the option on the command line.

The usage help for the above example looks like this:

Usage: negatable-options-demo [--[no-]verbose] [-XX:(+|-)PrintGCDetails]
                              [-XX:(+|-)UseG1GC]
      --[no-]verbose     Show verbose output
      -XX:(+|-)PrintGCDetails
                         Prints GC details
      -XX:(+|-)UseG1GC   Use G1 algorithm for GC

For *nix-style long options, aliases have the prefix no- to the given names, for example --no-verbose. For Java JVM-style options like -XX:+PrintGCDetails, the :+ is turned into :- and vice versa. Short option names are not given a negative alias by default. (This is customizable.)

If the negated form of the option is found, for example --no-verbose, the value is set to false. Otherwise, with a regular call, for example --verbose, the value is set to true.

Negatable options that are true by default

When a negatable option is true by default, give it both a defaultValue and a fallbackValue of "true". For example:

Java
@Option(names = "--backup", negatable = true,
  defaultValue = "true", fallbackValue = "true",
  description = "Make a backup. True by default.")
boolean backup;
Kotlin
@Option(names = ["--backup"], negatable = true,
  defaultValue = "true", fallbackValue = "true",
  description = ["Make a backup. True by default."])
var backup = true

The table below shows the value assigned to the annotated option field for a number of possible user input strings:

End user input      Option value
--------------      ------------
(no args)           true
--backup            true
--backup=true       true
--backup=false      false
--no-backup         false
--no-backup=true    false
--no-backup=false   true

3.6. Positional Parameters

Any command line arguments that are not subcommands or options (or option parameters) are interpreted as positional parameters. Positional parameters generally follow the options but from picocli 2.0, positional parameters can be mixed with options on the command line.

3.6.1. Explicit Index

Use the (zero-based) index attribute to specify exactly which parameters to capture. Array or collection fields can capture multiple values.

The index attribute accepts range values, so an annotation like @Parameters(index="2..4") captures the arguments at index 2, 3 and 4. Range values can be open-ended. For example, @Parameters(index="3..*") captures all arguments from index 3 and up.

For example:

Java
class PositionalParameters {
    @Parameters(index = "0")    InetAddress host;
    @Parameters(index = "1")    int port;
    @Parameters(index = "2..*") File[] files;

    @Parameters(hidden = true)  // "hidden": don't show this parameter in usage help message
    List<String> allParameters; // no "index" attribute: captures _all_ arguments
}
Kotlin
class PositionalParameters {
    @Parameters(index = "0")    lateinit var host: InetAddress
    @Parameters(index = "1")    var port = 0
    @Parameters(index = "2..*") lateinit var files: Array<File>

    @Parameters(hidden = true)   // "hidden": don't show this parameter in usage help message
    lateinit var allParameters: List<String> // no "index" attribute: captures _all_ arguments
}

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

Java
String[] args = { "localhost", "12345", "file1.txt", "file2.txt" };
PositionalParameters params = CommandLine.populateCommand(new PositionalParameters(), args);

assert params.host.getHostName().equals("localhost");
assert params.port == 12345;
assert Arrays.equals(params.files, new File[] {new File("file1.txt"), new File("file2.txt")});

assert params.allParameters.equals(Arrays.asList(args));
Kotlin
val args = arrayOf("localhost", "12345", "file1.txt", "file2.txt")
val params: PositionalParameters = CommandLine.populateCommand(PositionalParameters(), *args)

assert(params.host.getHostName().equals("localhost"))
assert(params.port === 12345)
assert(Arrays.equals(params.files, arrayOf(File("file1.txt"), File("file2.txt"))))

assert(params.allParameters.equals(Arrays.asList(*args)))

See Strongly Typed Everything for which types are supported out of the box and how to add custom types.

3.6.2. Omitting the Index

It is possible to omit the index attribute. 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, picocli assigns an index automatically, based on the other positional parameters defined in the same command.

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 JVM’s. 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.

See Automatic Parameter Indexes for details.

3.7. Mixing Options and Positional Parameters

From picocli 2.0, positional parameters can be specified anywhere on the command line, they no longer need to follow the options.

For example:

Java
class Mixed {
    @Parameters
    List<String> positional;

    @Option(names = "-o")
    List<String> options;
}
Kotlin
class Mixed {
    @Parameters
    lateinit var positional: List<String>

    @Option(names = ["-o"])
    lateinit var options: List<String>
}

Any command line argument that is not an option or subcommand is interpreted as a positional parameter.

Java
String[] args = { "param0", "-o", "AAA", "param1", "param2", "-o", "BBB", "param3" };
Mixed mixed = new Mixed();
new CommandLine(mixed).parseArgs(args);

assert mixed.positional.equals(Arrays.asList("param0", "param1", "param2", "param3");
assert mixed.options.equals   (Arrays.asList("AAA", "BBB"));
Kotlin
val args = arrayOf("param0", "-o", "AAA", "param1", "param2", "-o", "BBB", "param3")
val mixed = Mixed()
CommandLine(mixed).parseArgs(*args)

assert(mixed.positional == Arrays.asList("param0", "param1", "param2", "param3"))
assert(mixed.options == Arrays.asList("AAA", "BBB"))

3.8. Double dash (--)

Picocli offers built-in support for the End-of-Options delimiter (--), as defined in Guideline 10 of the POSIX Utility Syntax Guidelines.

When one of the command line arguments is just two dashes without any characters attached (--), picocli interprets all following arguments as positional parameters, even arguments that match an option name.

Java
class DoubleDashDemo {
    @Option(names = "-v")     boolean verbose;
    @Option(names = "-files") List<String> files;
    @Parameters               List<String> params;
}
Kotlin
class DoubleDashDemo {
    @Option(names = ["-v"])     var verbose = false
    @Option(names = ["-files"]) var files: List<String>? = null
    @Parameters                 lateinit var params: List<String>
}

The -- end-of-options delimiter clarifies which of the arguments are positional parameters:

Java
String[] args = { "-v", "--", "-files", "file1", "file2" };
DoubleDashDemo demo = new DoubleDashDemo();
new CommandLine(demo).parseArgs(args);

assert demo.verbose;
assert demo.files == null;
assert demo.params.equals(Arrays.asList("-files", "file1", "file2"));
Kotlin
val args = arrayOf("-v", "--", "-files", "file1", "file2")
val demo = DoubleDashDemo()
CommandLine(demo).parseArgs(*args)

assert(demo.verbose)
assert(demo.files == null)
assert(demo.params == Arrays.asList("-files", "file1", "file2"))

A custom delimiter can be configured with CommandLine.setEndOfOptionsDelimiter(String).

From picocli 4.3, an entry for -- can be shown in the options list of the usage help message of a command with the @Command(showEndOfOptionsDelimiterInUsageHelp = true) annotation. See Show End of Options for details.

3.9. @-files

3.9.1. Argument Files for Long Command Lines

Users sometimes run into system limitations on the length of a command line when creating a command line with lots of options or with long arguments for options.

Starting from v2.1.0, picocli supports "argument files" or "@-files". Argument files are files that themselves contain arguments to the command. When picocli encounters an argument beginning with the character @, it expands the contents of that file into the argument list.

An argument file can include options and positional parameters in any combination. The arguments within a file can be space-separated or newline-separated. If an argument contains embedded whitespace, put the whole argument in double or single quotes. Within quoted values, backslashes need to be escaped with another backslash.

For example, it is possible to have a path with a space, such as c:\Program Files that can be specified as either "c:\\Program Files" or, to avoid an escape, c:\Program" "Files.

Argument files do have a limitation: parameter or option values enclosed in quotes must not be preceded by an equal sign. Something like myoption="foo bar" does not work inside an argument file. To work around this, either omit the equal sign (myoption "foo bar") or enclose the whole expression in quotes ("myoption=\"foo bar\"").

Lines starting with # are comments and are ignored. The comment character can be configured with CommandLine.setAtFileCommentChar(Character), and comments can be switched off by setting the comment character to null.

The file may itself contain additional @-file arguments; any such arguments will be processed recursively.

If the file does not exist, or cannot be read, then the argument will be treated literally, and not removed. Multiple @-files may be specified on the command line. The specified path may be relative (to the current directory) or absolute.

For example, suppose a file with arguments exists at /home/foo/args, with these contents:

# This line is a comment and is ignored.
ABC -option=123
'X Y Z'

A command may be invoked with the @file argument, like this:

java MyCommand @/home/foo/args

The above will be expanded to the contents of the file:

java MyCommand ABC -option=123 "X Y Z"

Handling of UTF-8 encoded argument files is tricky on Windows OS with Java up to version 17. Either use -Dfile.encoding=UTF-8 as VM argument or set the environment variable JAVA_TOOL_OPTIONS. So both command calls given below will work on a Windows command line:

java -Dfile.encoding=UTF-8 MyCommand @/home/foo/args
SET JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF-8
java MyCommand @/home/foo/args
In Java 18 the default encoding was changed from a system dependent value to always be UTF-8. If you require the old behavior, you need to set the file.encoding system property to the value COMPAT.

@-file expansion can be switched off by calling CommandLine::setExpandAtFiles with false. If turned on, you can still pass a real parameter with an initial @ character by escaping it with an additional @ symbol, e.g. @@somearg will become @somearg and not be subject to expansion.

This feature is similar to the 'Command Line Argument File' processing supported by gcc, javadoc and javac. The documentation for these tools has more details. See for example the documentation for java Command-Line Argument Files.

If you think your users may find this feature (@files) useful, you could consider adding an option to your application that generates an @file for the specified arguments. The picocli-examples module on GitHub has an example to get you started.

3.9.2. @-files Usage Help

From picocli 4.2, an entry for @<filename> can be shown in the options and parameters list of the usage help message of a command with the @Command(showAtFileInUsageHelp = true) annotation. See Show At Files for details.

3.9.3. Simplified Format

From picocli 3.8.1, a simpler argument file format is also supported where every line (except empty lines and comment lines) is interpreted as a single argument. Arguments containing whitespace do not need to be quoted, but it is not possible to have arguments with embedded newlines or to have empty string arguments without quotes. From picocli 3.9, this simpler argument format is fully compatible with JCommander's @-file argument file format.

You can tell picocli to use the simplified argument file format programmatically with CommandLine.setUseSimplifiedAtFiles(true), or by setting system property picocli.useSimplifiedAtFiles without a value or with value "true" (case-insensitive). The system property is useful to allow end users control over the format.

4. Strongly Typed Everything

When command line options and positional parameters are mapped to the annotated fields, the text value is converted to the type of the annotated field.

4.1. Built-in Types

Out of the box, picocli can convert command line argument strings to a number of common data types.

Most of the built-in types work with Java 5, but picocli also has some default converters for Java 7 types like Path and Java 8 types like Duration, etc. These converters are loaded using reflection and are only available when running on a Java version that supports them. See the below list for details.

  • any Java primitive type or their wrapper

  • any enum

  • String, StringBuilder, CharSequence

  • java.math.BigDecimal, java.math.BigInteger

  • java.nio.Charset

  • java.io.File

  • java.net.InetAddress

  • java.util.regex.Pattern

  • java.util.Date (for values in "yyyy-MM-dd" format)

  • java.net.URL, java.net.URI

  • java.util.UUID

  • java.lang.Class (from picocli 2.2, for the fully qualified class name)

  • java.nio.ByteOrder (from picocli 2.2, for the Strings "BIG_ENDIAN" or "LITTLE_ENDIAN")

  • java.util.Currency (from picocli 2.2, for the ISO 4217 code of the currency)

  • java.net.NetworkInterface (from picocli 2.2, for the InetAddress or name of the network interface)

  • java.util.TimeZone (from picocli 2.2, for the ID for a TimeZone)

Converters loaded using reflection:

  • java.nio.file.Path (from picocli 2.2, requires Java 7 or higher)

  • java.time value objects: Duration, Instant, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Period, Year, YearMonth, ZonedDateTime, ZoneId, ZoneOffset (from picocli 2.2, requires Java 8 or higher, invokes the parse method of these classes)

  • java.sql.Time (for values in any of the "HH:mm", "HH:mm:ss", "HH:mm:ss.SSS", or "HH:mm:ss,SSS" formats)

  • java.sql.Timestamp (from picocli 2.2, for values in the "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd HH:mm:ss.fffffffff" formats)

  • java.sql.Connection (from picocli 2.2, for a database url of the form jdbc:subprotocol:subname)

  • java.sql.Driver (from picocli 2.2, for a database URL of the form jdbc:subprotocol:subname)

Sometimes loading converters with reflection is not desirable. Use system property picocli.converters.excludes to specify a comma-separated list of fully qualified class names for which the converter should not be loaded. Regular expressions are supported. For example, invoking the program with -Dpicocli.converters.excludes=java.sql.Ti.* will not load type converters for java.sql.Time and java.sql.Timestamp.

4.2. Custom Type Converters

Register a custom type converter to handle data types other than the above built-in ones.

4.2.1. Single Parameter Type Converters

Custom converters need to implement the picocli.CommandLine.ITypeConverter interface:

public interface ITypeConverter<K> {
    /**
     * Converts the specified command line argument value to some domain object.
     * @param value the command line argument String value
     * @return the resulting domain object
     * @throws Exception an exception detailing what went wrong during the conversion
     */
    K convert(String value) throws Exception;
}

For example:

import javax.crypto.Cipher;

class CipherConverter implements ITypeConverter<Cipher> {
    public Cipher convert(String value) throws Exception {
        return Cipher.getInstance(value);
    }
}

Custom type converters can be specified for a specific option or positional parameter with the converter annotation attribute. This is described in more detail in the Option-specific Type Converters section, but here is a quick example:

Java
class App {
    @Option(names = "-a", converter = CipherConverter.class)
    javax.crypto.Cipher cipher;
}
Kotlin
class App {
    @Option(names = ["-a"], converter = [CipherConverter::class])
    lateinit var cipher: javax.crypto.Cipher
}
Groovy
class App {
    @Option(names = "-a", converter = CipherConverter.class)
    def cipher
}
Groovy Script
class App {
    @Option(names = "-a", converter = [ // requires Groovy 3.0.7
            { { str -> Cipher.getInstance(str) } as ITypeConverter }
    ])
    def cipher
}

Alternatively, custom type converters can be registered per type in each command with the CommandLine.registerConverter(Class<K> cls, ITypeConverter<K> converter) method. All options and positional parameters with the specified type will be converted by the specified converter.

After registering custom converters, call the execute(String…​) or parseArgs(String…​) method on the CommandLine instance where the converters are registered. (The static populateCommand method cannot be used.) For example:

Java
class App {
    @Parameters java.util.Locale locale;
    @Option(names = "-a") javax.crypto.Cipher cipher;
}
Kotlin
import java.util.Locale
import javax.crypto.Cipher
// ...

class App {
    @Parameters
    lateinit var locale: Locale

    @Option(names = ["-a"])
    lateinit var cipher: Cipher
}
Java 8 lambdas make it easy to register custom converters:
Java
App app = new App();
CommandLine commandLine = new CommandLine(app)
    .registerConverter(Locale.class, s -> new Locale.Builder().setLanguageTag(s).build())
    .registerConverter(Cipher.class, s -> Cipher.getInstance(s));

commandLine.parseArgs("-a", "AES/CBC/NoPadding", "en-GB");
assert app.locale.toLanguageTag().equals("en-GB");
assert app.cipher.getAlgorithm().equals("AES/CBC/NoPadding");
Kotlin
val app = App()
val commandLine = CommandLine(app)
    .registerConverter(Locale::class.java) {
        s: String? -> Locale.Builder().setLanguageTag(s).build()
    }
    .registerConverter(Cipher::class.java) {
        Cipher.getInstance(it)
    }

commandLine.parseArgs("-a", "AES/CBC/NoPadding", "en-GB")
assert(app.locale.toLanguageTag() == "en-GB")
assert(app.cipher.algorithm == "AES/CBC/NoPadding")
Note on subcommands: the specified converter will be registered with the CommandLine object and all subcommands (and nested sub-subcommands) that were added before the converter was registered. Subcommands added later will not have the converter added automatically. To ensure a custom type converter is available to all subcommands, register the type converter last, after adding subcommands.

4.2.2. Multi Parameter Type Converters

Some types take more than one parameter. The IParameterConsumer interface can be used to implement a multi-parameter type converter.

Java
@Command(name = "set-position")
class SetPositionCommand {
    @Parameters(parameterConsumer = PointConverter.class)
    private Point position;

    static class PointConverter implements IParameterConsumer {
        public void consumeParameters(Stack<String> args,
                                      ArgSpec argSpec,
                                      CommandSpec commandSpec) {
            if (args.size() < 2) {
                throw new ParameterException(commandSpec.commandLine(),
                        "Missing coordinates for Point. Please specify 2 coordinates."));
            }
            int x = Integer.parseInt(args.pop());
            int y = Integer.parseInt(args.pop());
            argSpec.setValue(new Point(x, y));
        }
    }
}
Kotlin
@Command(name = "set-position")
class SetPositionCommand {
    @Parameters(parameterConsumer = PointConverter::class)
    private lateinit var position: Point

    class PointConverter : IParameterConsumer {
        override fun consumeParameters(args: Stack<String>,
                                       argSpec: ArgSpec,
                                       commandSpec: CommandSpec) {
            if (args.size < 2) {
                throw ParameterException(commandSpec.commandLine(),
                    "Missing coordinates for Point. Please specify 2 coordinates.")
            }
            val x = args.pop().toInt()
            val y = args.pop().toInt()
            argSpec.setValue(Point(x, y))
        }
    }
}

See the sections on Custom Parameter Processing for more details.

Make sure any nested classes are static, or picocli will not be able to instantiate them.

4.3. Handling Invalid Input

If the user specifies invalid input, custom type converters should throw an exception. Any exception is fine, and will result in a message like the below being displayed to the user:

Invalid value for option '--socket-address': cannot convert 'xxxinvalidinput' to InetSocketAddress (java.lang.IllegalArgumentException: Invalid format: must be 'host:port' but was 'xxxinvalidinput')

The above error message is generic and is reasonable for many exceptions, but sometimes you want more control over the error message displayed to the user. To achieve this, throw a picocli.CommandLine.TypeConversionException instead. When a TypeConversionException is thrown, picocli will show an error message that indicates the problematic option, followed by the exception message text. The resulting output looks something like this:

Invalid value for option '--socket-address': Invalid format: must be 'host:port' but was 'xxxinvalidinput'

Below is an example custom converter that throws a TypeConversionException:

Java
import java.net.InetSocketAddress;

class InetSocketAddressConverter implements ITypeConverter<InetSocketAddress> {
    @Override
    public InetSocketAddress convert(String value) {
        int pos = value.lastIndexOf(':');
        if (pos < 0) {
            throw new TypeConversionException(
                "Invalid format: must be 'host:port' but was '" + value + "'");
        }
        String adr = value.substring(0, pos);
        int port = Integer.parseInt(value.substring(pos + 1));
        return new InetSocketAddress(adr, port);
    }
}
Kotlin
import java.net.InetSocketAddress
// ...

class InetSocketAddressConverter : ITypeConverter<InetSocketAddress> {
    override fun convert(value: String): InetSocketAddress {
        val pos = value.lastIndexOf(':')
        if (pos < 0) {
            throw CommandLine.TypeConversionException(
                "Invalid format: must be 'host:port' but was '$value'")
        }
        val adr = value.substring(0, pos)
        val port = value.substring(pos + 1).toInt()
        return InetSocketAddress(adr, port)
    }
}

The picocli-examples module on GitHub has a minimal working example which you can run in our online-editor.

Note that when an option has variable arity, the picocli parser cannot tell whether each next argument belongs to that option or to a positional parameter. It will try to assign to the option first, but it will take the type conversion exception to mean that it has reached the end of the parameters for that option, and this argument must be assigned to a positional parameter instead.

If it then cannot find a positional parameter, an unmatched argument error is shown to the end user instead.

4.4. Option-specific Type Converters

Picocli 2.2 added a converter attribute to the @Option and @Parameter annotations. This allows a specific option or positional parameter to use a different converter than would be used by default based on the type of the field.

For example, for a specific field you may want to use a converter that maps the constant names defined in java.sql.Types to the int value of these constants, but any other int fields should not be affected by this and should continue to use the standard int converter that parses numeric values.

Example usage:

Java
class App {
    @Option(names = "--sqlType", converter = SqlTypeConverter.class)
    int sqlType;
}
Kotlin
class App {
    @Option(names = ["--sqlType"], converter = [SqlTypeConverter::class])
    var sqlType = 0
}

Example implementation:

Java
class SqlTypeConverter implements ITypeConverter<Integer> {
    public Integer convert(String value) throws Exception {
        switch (value) {
            case "ARRAY"  : return Types.ARRAY;
            case "BIGINT" : return Types.BIGINT;
            case "BINARY" : return Types.BINARY;
            case "BIT"    : return Types.BIT;
            case "BLOB"   : return Types.BLOB;
            // ...
        }
    }
}
Kotlin
class SqlTypeConverter : ITypeConverter<Int> {
    @Throws(Exception::class)
    override fun convert(value: String): Int {
        when (value) {
            "ARRAY"  -> return Types.ARRAY
            "BIGINT" -> return Types.BIGINT
            "BINARY" -> return Types.BINARY
            "BIT"    -> return Types.BIT
            "BLOB"   -> return Types.BLOB
            // ...
        }
    }
}

This may also be useful for applications that need a custom type converter but want to use the static convenience methods (populateCommand, run, call, invoke). The converter annotation does not require a CommandLine instance so it can be used with the static convenience methods.

Type converters declared with the converter attribute need to have a public no-argument constructor to be instantiated, unless a Custom Factory is installed to instantiate classes.

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

4.5. Arrays, Collections, Maps

Starting from picocli 2.0, the type attribute is no longer necessary for Collection and Map fields: picocli will infer the collection element type from the generic type. (The type attribute still works as before, it is just optional in most cases.)

4.5.1. Arrays and Collections

Multiple parameters can be captured together in a single array or Collection field. The array or collection elements can be any type for which a converter is registered. For example:

Java
import java.util.regex.Pattern;
import java.io.File;

class Convert {
    @Option(names = "-patterns", description = "the regex patterns to use")
    Pattern[] patterns;

    @Parameters(/* type = File.class, */ description = "the files to convert")
    List<File> files; // picocli infers type from the generic type
}
Kotlin
import java.io.File
import java.util.regex.Pattern
// ...

class Convert {
    @Option(names = ["-patterns"], description = ["the regex patterns to use"])
    lateinit var patterns: Array<Pattern>

    @Parameters( /* type = [File::class], */ description = ["the files to convert"])
    lateinit var files: List<File> // picocli infers type from the generic type
}
Java
String[] args = { "-patterns", "a*b", "-patterns", "[a-e][i-u]", "file1.txt", "file2.txt" };
Convert convert = CommandLine.populateCommand(new Convert(), args);

// convert.patterns now has two Pattern objects
// convert.files now has two File objects
Kotlin
val args = arrayOf("-patterns", "a*b", "-patterns", "[a-e][i-u]", "file1.txt", "file2.txt")
val convert = CommandLine.populateCommand(Convert(), *args)

// convert.patterns now has two Pattern objects
// convert.files now has two File objects
If a collection is returned from a type converter, the contents of the collection are added to the field or method parameter, not the collection itself.

If the field or method parameter is null, picocli will instantiate it when the option or positional parameter is successfully matched. If the Collection type is not a concrete class, picocli will make a best effort to instantiate it based on the field type: List → ArrayList, SortedSet → TreeSet, Set → LinkedHashSet, Queue → LinkedList, otherwise, ArrayList.

Multi-value options and positional parameters can be defined with a split regular expression to allow end users to specify multiple values in a single parameter. See the Split Regex section for details.

4.5.2. Maps

Picocli 1.0 introduced support for Map fields similar to Java’s system properties -Dkey=value or Gradle properties -P myprop=myvalue.

Map fields may have any type for their key and value as long as a converter is registered for both the key and the value type. Key and value types are inferred from the map’s generic type parameters. For example:

Java
import java.net.InetAddress;
import java.net.Proxy.Type;
import java.util.concurrent.TimeUnit;

class MapDemo {
    @Option(names = {"-p", "--proxyHost"})
    Map<Proxy.Type, InetAddress> proxies;

    @Option(names = {"-u", "--timeUnit"})
    Map<TimeUnit, Long> timeout;
}
Kotlin
import java.net.InetAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
// ...

class MapDemo {
    @Option(names = ["-p", "--proxyHost"])
    lateinit var proxies: Map<Proxy.Type, InetAddress>

    @Option(names = ["-u", "--timeUnit"])
    lateinit var timeout: Map<TimeUnit, Long>
}

Map options may be specified multiple times with different key-value pairs. (See Multiple Values.)

<command> -p HTTP=123.123.123.123 --proxyHost SOCKS=212.212.212.212
<command> -uDAYS=3 -u HOURS=23 -u=MINUTES=59 --timeUnit=SECONDS=13

If the annotated field is null, picocli will instantiate it when the option or positional parameter is matched. If the Map type is not a concrete class, picocli will instantiate a LinkedHashMap to preserve the input ordering.

On the command line, the key and the value must be separated by an = character.

Map options and positional parameters can be defined with a split regular expression to allow end users to specify multiple values in a single parameter. See the Split Regex section for details.

4.5.3. Key-only map parameters

By default, picocli expects Map options and positional parameters to look like key=value, that is, the option parameter or positional parameter is expected to have a key part and a value part, separated by an = character. If this is not the case, picocli shows a user-facing error message: Value for …​ should be in KEY=VALUE format but was …​.

From picocli 4.6, applications can specify a mapFallbackValue to allow end users to specify only the key part. The specified mapFallbackValue is put into the map when end users do specify only a key. The value type can be wrapped in a java.util.Optional. For example:

Java
@Option(names = {"-P", "--properties"}, mapFallbackValue = Option.NULL_VALUE)
Map<String, Optional<Integer>> properties;

@Parameters(mapFallbackValue = "INFO", description = "... ${MAP-FALLBACK-VALUE} ...")
Map<Class<?>, LogLevel> logLevels;
Kotlin
@Option(names = ["-P", "--properties"], mapFallbackValue = Option.NULL_VALUE)
lateinit var properties: Map<String, Optional<Int>>

@Parameters(mapFallbackValue = "INFO", description = "... ${MAP-FALLBACK-VALUE} ...")
lateinit var logLevels: Map<Class<?>, LogLevel>

This allows input like the following:

<cmd> --properties=key1 -Pkey2 -Pkey3=3 org.myorg.MyClass org.myorg.OtherClass=DEBUG

The above input would give the following results:

properties = [key1: Optional.empty, key2: Optional.empty, key3: Optional[3]]
logLevels  = [org.myorg.MyClass: INFO, org.myorg.OtherClass: DEBUG]

Note that the option description may contain the ${MAP-FALLBACK-VALUE} variable which will be replaced with the actual map fallback value when the usage help is shown.

4.5.4. System Properties

A common requirement for command line applications is to support the -Dkey=value syntax to allow end users to set system properties.

The example below uses the Map type to define an @Option-annotated method that delegates all key-value pairs to System::setProperty. Note the use of mapFallbackValue = "" to allow key-only option parameters.

Java
class SystemPropertiesDemo {
    @Option(names = "-D", mapFallbackValue = "") // allow -Dkey
    void setProperty(Map<String, String> props) {
        props.forEach((k, v) -> System.setProperty(k, v));
    }
}
Kotlin
class SystemPropertiesDemo {
    @Option(names = ["-D"], mapFallbackValue = "") // allow -Dkey
    fun setProperty(props: Map<String, String>) {
        props.forEach { (k: String, v: String) -> System.setProperty(k, v) }
    }
}

4.6. Optional<T>

From version 4.6, picocli supports single-value types wrapped in a java.util.Optional container object when running on Java 8 or higher. If the option or positional parameter was not specified on the command line, picocli assigns the value Optional.empty() instead of null. For example:

Java
@Option(names = "-x")
Optional<Integer> x;

@Option(names = "-D", mapFallbackValue = Option.NULL_VALUE)
Map<String, Optional<Integer>> map;
Kotlin
@Option(names = ["-x"])
lateinit var x: Optional<Int>

@Option(names = ["-D"], mapFallbackValue = Option.NULL_VALUE)
lateinit var map: Map<String, Optional<Int>>
Picocli has only limited support for java.util.Optional types: only single-value types, and the values in a Map (but not the keys!) can be wrapped in an Optional container. java.util.Optional cannot be combined with arrays or other Collection classes.

4.7. Abstract Field Types

The field’s type can be an interface or an abstract class. The type attribute can be used to control for each field what concrete class the string value should be converted to. For example:

Java
class App {
    @Option(names = "--big", type = BigDecimal.class) // concrete Number subclass
    Number[] big; // array type with abstract component class

    @Option(names = "--small", type = Short.class) // other Number subclass
    Number[] small;

    @Parameters(type = StringBuilder.class) // StringBuilder implements CharSequence
    CharSequence address; // interface type
}
Kotlin
class App {
    @Option(names = ["--big"], type = [BigDecimal::class]) // concrete Number subclass
    lateinit var big: Array<Number> // array type with abstract component class

    @Option(names = ["--small"], type = [Short::class]) // other Number subclass
    lateinit var small: Array<Number>

    @Parameters(type = [StringBuilder::class]) // StringBuilder implements CharSequence
    lateinit var address: CharSequence // interface type
}

4.7.1. Maps and Collections with Abstract Elements

For raw maps and collections, or when using generics with unbounded wildcards like Map<?, ?>, or when the type parameters are themselves abstract classes like List<CharSequence> or Map<? extends Number, ? super Number>, there is not enough information to convert to a stronger type. By default, the raw String values are added as is to such collections.

The type attribute can be specified to convert to a stronger type than String. For example:

Java
class TypeDemo {
    @Option(names = "-x")  // not enough information to convert
    Map<?, ?> weaklyTyped; // String keys and values are added as is

    @Option(names = "-y", type = {Short.class, BigDecimal.class})
    Map<? extends Number, ? super Number> stronglyTyped;

    @Option(names = "-s", type = CharBuffer.class)
    List<CharSequence> text;
}
Kotlin
class TypeDemo {
    @Option(names = ["-x"])              // not enough information to convert
    lateinit var weaklyTyped: Map<*, *> // String keys and values are added as is

    @Option(names = ["-y"], type = [Short::class, BigDecimal::class])
    lateinit var stronglyTyped: Map<out Number, Number>

    @Option(names = ["-s"], type = [CharBuffer::class])
    lateinit var text: List<CharSequence>
}

4.8. Enum Types

It is encouraged to use enum types for options or positional parameters with a limited set of valid values. Not only will picocli validate the input, it allows you to show all values in the usage help message with @Option(description = "Valid values: ${COMPLETION-CANDIDATES}"). It also allows command line completion to suggest completion candidates for the values of this option.

Enum value matching is case-sensitive by default, but as of picocli 3.4 this can be controlled with CommandLine::setCaseInsensitiveEnumValuesAllowed and CommandSpec::caseInsensitiveEnumValuesAllowed.

5. Default Values

It is possible to define a default value for an option or positional parameter, that is assigned when the user did not specify this option or positional parameter on the command line.

Configuring a default value guarantees that the @Option or @Parameters-annotated field will get set, annotated method will get called, and, when using the programmatic API, that the ArgSpec.setValue method will get invoked, even when the option or positional parameter was not specified on the command line.

5.1. defaultValue Annotation

The recommended way to give an option or positional parameter a default value is to use the defaultValue annotation attribute. This works correctly with argument groups, @Option and @Parameters-annotated methods, and allows annotation processors to detect and use default values.

For @Option and @Parameters-annotated methods and @Command-annotated methods, there is no alternative but to use the defaultValue annotation attribute. For example, for an annotated interface:

Java
interface Spec {
    @Option(names = "-c", defaultValue = "123", description = "... ${DEFAULT-VALUE} ...")
    int count();
}
Kotlin
interface Spec {
    @Option(names = ["-c"], defaultValue = "123", description = ["... \${DEFAULT-VALUE} ..."])
    fun count(): Int
}

Example of using the defaultValue attribute in the option of a command method:

Java
class CommandMethod {
    @Command(description = "Do something.")
    void doit(@Option(names = "-c", defaultValue = "123") int count) {
        // ...
    }
}
Kotlin
class CommandMethod {
    @Command(description = ["Do something."])
    fun doit(@Option(names = ["-c"], defaultValue = "123") count: Int) {
        // ...
    }
}

Note that you can use the ${DEFAULT-VALUE} variable in the description of the option or positional parameter and picocli will show the actual default value.

5.2. Field Values

For annotated fields, it is possible to declare the field with a value:

Java
@Option(names = "-c", description = "The count (default: ${DEFAULT-VALUE})")
int count = 123; // default value is 123
Kotlin
@Option(names = ["-c"], description = ["The count (default: \${DEFAULT-VALUE})"])
var count = 123 // default value is 123

Defining a default value by assigning a value at the field declaration has limitations:

  • when the option is used in an argument group, the usage help cannot show the default value

  • picocli’s annotation processors can only detect default values in annotations, not in the field declaration. Your application may not work correctly with future features like documentation generated from the annotations.

5.3. Variables in Default Values

The default value itself may also contain variables. For example:

Java
@Option(names = "-c", defaultValue = "${COUNT:-123}")
int count;
Kotlin
@Option(names = ["-c"], defaultValue = "\${COUNT:-123}")
lateinit count: int

Picocli will look up the value of the COUNT variable in the system properties, environment variables, and resource bundle, and finally use value 123 if no value is found for any of these lookups.

5.4. Default Provider

Finally, you can specify a default provider in the @Command annotation:

Java
@Command(defaultValueProvider = MyDefaultProvider.class)
class MyCommand // ...
Kotlin
@Command(defaultValueProvider = MyDefaultProvider::class)
class MyCommand // ...

The default provider allows you to get default values from a configuration file or some other central place. Default providers need to implement the picocli.CommandLine.IDefaultValueProvider interface:

public interface IDefaultValueProvider {

    /**
     * Returns the default value for an option or positional parameter or {@code null}.
     * The returned value is converted to the type of the option/positional parameter
     * via the same type converter used when populating this option/positional
     * parameter from a command line argument.
     *
     * @param argSpec the option or positional parameter, never {@code null}
     * @return the default value for the option or positional parameter, or {@code null} if
     *       this provider has no default value for the specified option or positional parameter
     * @throws Exception when there was a problem obtaining the default value
     */
    String defaultValue(ArgSpec argSpec) throws Exception;
}

See the default provider examples for example implementations.

If the command has a default provider configured, and the option or positional parameter has a default value configured, then picocli will first try to find the value in the default provider. If the default provider has no value for that option or positional parameter, then the default value configured on the option or positional parameter is used.

5.5. PropertiesDefaultProvider

From picocli 4.1, applications can use the built-in PropertiesDefaultProvider implementation that loads default values from a properties file.

By default, this implementation tries to find a properties file named .${COMMAND-NAME}.properties in the user home directory or in the classpath, where ${COMMAND-NAME} is the name of the command. If a command has aliases in addition to its name, these aliases are also used to try to find the properties file. For example:

Java
import picocli.CommandLine.PropertiesDefaultProvider;
// ...
@Command(name = "git", defaultValueProvider = PropertiesDefaultProvider.class)
class Git { }
Kotlin
import picocli.CommandLine.PropertiesDefaultProvider;
// ...
@Command(name = "git", defaultValueProvider = PropertiesDefaultProvider::class)
class Git { }

The above will try to load default values from new File(System.getProperty("user.home"), ".git.properties"). The location of the properties file can also be controlled with system property "picocli.defaults.${COMMAND-NAME}.path" ("picocli.defaults.git.path" in this example), in which case the value of the property must be the path to the file containing the default values. Finally, picocli will try to load the .git.properties file from the classpath.

The location of the properties file may also be specified programmatically. For example:

Java
CommandLine cmd = new CommandLine(new MyCommand());
File defaultsFile = new File("path/to/config/mycommand.properties");
cmd.setDefaultValueProvider(new PropertiesDefaultProvider(defaultsFile));
cmd.execute(args);
Kotlin
val cmd = CommandLine(MyCommand())
val defaultsFile = File("path/to/config/mycommand.properties")
cmd.defaultValueProvider = PropertiesDefaultProvider(defaultsFile)
cmd.execute(*args)

5.5.1. PropertiesDefaultProvider Format

The PropertiesDefaultProvider expects the properties file to be in the standard java .properties format.

For options, the key is either the descriptionKey, or the option’s longest name, without the prefix. So, for an option --verbose, the key would be verbose, and for an option /F, the key would be F.

For positional parameters, the key is either the descriptionKey, or the positional parameter’s param label.

End users may not know what the descriptionKey of your options and positional parameters are, so be sure to document that with your application.

5.5.2. Subcommands Default Values

The default values for options and positional parameters of subcommands can be included in the properties file for the top-level command, so that end users need to maintain only a single file. This can be achieved by prefixing the keys for the options and positional parameters with their command’s qualified name. For example, to give the git commit command’s --cleanup option a default value of strip, define a key of git.commit.cleanup and assign it a default value:

# /home/remko/.git.properties
git.commit.cleanup = strip

5.6. fallbackValue Annotation

If an option is defined with arity = "0..1", it may or may not have a parameter value. If such an option is specified without a value on the command line, it is assigned the fallback value.

The fallbackValue annotation attribute was introduced in picocli 4.0; prior to this, (from picocli 2.3) an empty String was assigned.

This is different from the defaultValue, which is assigned if the option is not specified at all on the command line.

For example:

Java
class FallbackValueDemo implements Runnable {
    @Option(names = "-x", arity = "0..1",
            defaultValue = "-1", fallbackValue = "-2",
            description = "Option with optional parameter. Default: ${DEFAULT-VALUE}, " +
                          "if specified without parameter: ${FALLBACK-VALUE}")
    int x;

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

    public static void main(String... args) {
       new CommandLine(new FallbackValueDemo()).execute(args);
    }
}
Kotlin
class FallbackValueDemo : Runnable {
    @Option(names = ["-x"], arity = "0..1",
            defaultValue = "-1", fallbackValue = "-2",
            description = ["Option with optional parameter. Default: \${DEFAULT-VALUE}, " +
                           "if specified without parameter: \${FALLBACK-VALUE}"])
    var x = 0

    override fun run() { println("x = $x") }
}

fun main(args: Array<String>) {
    CommandLine(FallbackValueDemo()).execute(*args)
}

Gives the following results:

java FallbackValueDemo -x 100
x = 100

java FallbackValueDemo -x
x = -2

java FallbackValueDemo
x = -1

Any String value is converted to the type of the option before it is assigned to the option. Options and positional parameters may define a custom type converter if necessary.

Note that the option description may contain the ${FALLBACK-VALUE} variable which will be replaced with the actual fallback value when the usage help is shown.

Boolean options can also define a fallbackValue to specify the value that should be set when the option is matched on the command line, regardless of the default value. This can be useful when the default is configurable by the end user, for example.

5.7. Was a Value Defaulted?

Sometimes an application is interested in knowing whether an option value was specified on the command line, or whether the default value was assigned.

You can use ParseResult::hasMatchedOption to detect whether an option was actually matched on the command line, and ParseResult::matchedOptionValue to get the (type-converted) value that was specified on the command line. OptionSpec::getValue will return the value assigned (which may be a command line argument or may be the default value).

For example:

Java
@Command(name = "defaults", mixinStandardHelpOptions = true, version = "defaults 0.1")
public class DefaultValueDemo implements Runnable {

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

    @Option(names = "-y", defaultValue = "20")
    int y;

    @Spec CommandSpec spec;

    @Override
    public void run() {
        ParseResult pr = spec.commandLine().getParseResult();

        for (OptionSpec option : spec.options()) {
            String name = option.longestName();
            System.out.printf("%s was specified: %s%n", name, pr.hasMatchedOption(option));
            System.out.printf("%s=%s (-1 means this option was not matched on command line)%n",
                    name, pr.matchedOptionValue(name, -1));
            System.out.printf("%s=%s (arg value or default)%n", name, option.getValue());
            System.out.println();
        }
    }

    public static void main(String[] args) {
        new CommandLine(new DefaultValueDemo()).execute(args);
    }
}
Kotlin
@Command(name = "defaults", mixinStandardHelpOptions = true, version = ["defaults 0.1"])
class DefaultValueDemo<T> : Runnable {

    @Option(names = ["-x"])
    var x = 10

    @Option(names = ["-y"], defaultValue = "20")
    var y = 0

    @Spec lateinit var spec: CommandSpec

    override fun run() {
        val pr: ParseResult = spec.commandLine().parseResult

        for (option in spec.options()) {
            val name = option.longestName()
            println("$name was specified: ${pr.hasMatchedOption(option)}")
            println("$name=${pr.matchedOptionValue(name, -1)} " +
                    "(-1 means this option was not matched on command line)")
            println("$name=${option.getValue<T>()} (arg value or default)")
            println()
        }
    }
}

fun main(args: Array<String>) {
    CommandLine(DefaultValueDemo<Any>()).execute(*args)
}

5.8. Null Default Values

To assign null as default value, applications can use the value Option.NULL_VALUE in the annotations for defaultValue and fallbackValue.

If the type of the option or positional parameter is Optional<T>, then picocli assigns the default value Optional.empty() instead of null.

For example:

Java
@Option(names = "-x", defaultValue = Option.NULL_VALUE)
Integer x; // default value is null

@Option(names = "-y", defaultValue = Option.NULL_VALUE)
Optional<Integer> y; // default value is Optional.empty()

@Option(names = "-D", mapFallbackValue = Option.NULL_VALUE)
Map<String, Optional<Integer>> map; // "-Dkey" results in ["key" : Optional.empty()]
Kotlin
@Option(names = ["-x"], defaultValue = Option.NULL_VALUE)
lateinit var x: Int // default value is null

@Option(names = ["-y"], defaultValue = Option.NULL_VALUE)
lateinit var y: Optional<Int> // default value is Optional.empty()

@Option(names = ["-D"], mapFallbackValue = Option.NULL_VALUE)
lateinit var map: Map<String, Optional<Int>> // "-Dkey" results in ["key" : Optional.empty()]

6. Multiple Values

Multi-valued options and positional parameters are annotated fields that can capture multiple values from the command line.

6.1. Multiple Occurrences

6.1.1. Repeated Options

The simplest way to create a multi-valued option is to declare an annotated field whose type is an array, collection or a map.

Java
@Option(names = "-option")
int[] values;
Kotlin
@Option(names = ["-option"])
lateinit var values: IntArray

Users may specify the same option multiple times. For example:

<command> -option 111 -option 222 -option 333

Each value is appended to the array or collection.

6.1.2. Multiple Positional Parameters

Similarly for multi-valued positional parameters:

Java
@Parameters
List<TimeUnit> units;
Kotlin
@Parameters
lateinit var units: List<TimeUnit>

Users may specify multiple positional parameters. For example:

<command> SECONDS HOURS DAYS

Again, each value is appended to the array or collection.

6.1.3. Repeated Boolean Options

Boolean options with multiple values are supported as of picocli 2.1.0.

Java
@Option(names = "-v", description = { "Specify multiple -v options to increase verbosity.",
                                      "For example, `-v -v -v` or `-vvv`"})
boolean[] verbosity;
Kotlin
@Option(names = ["-v"], description = ["Specify multiple -v options to increase verbosity.",
                                        "For example, `-v -v -v` or `-vvv`"])
lateinit var verbosity: BooleanArray

Users may specify multiple boolean flag options without parameters. For example:

<command> -v -v -v -vvv

The above example results in six true values being added to the verbosity array.

6.2. Split Regex

Options and parameters may also specify a split regular expression used to split each option parameter into smaller substrings. Each of these substrings is converted to the type of the collection or array. See Arrays and Collections.

Java
@Option(names = "-option", split = ",")
int[] values;
Kotlin
@Option(names = ["-option"], split = ",")
lateinit var values: IntArray

A single command line argument like the following will be split up and three int values are added to the array:

-option 111,222,333

Similarly for Maps:

Java
@Option(names = "-fix", split = "\\|", splitSynopsisLabel = "|")
Map<Integer, String> message;
Kotlin
@Option(names = ["-fix"], split = "\\|", splitSynopsisLabel = "|")
lateinit var message: Map<Int, String>

With the above option, command line arguments like the following are interpreted as a set of key-value pairs instead of a single string:

Note: end users need to quote the option parameter to prevent the vertical bar | characters from being interpreted by the shell as "pipe" directives to connect processes.
-fix "8=FIX.4.4|9=69|35=A|49=MBT|56=TargetCompID|34=9|52=20130625-04:05:32.682|98=0|108=30|10=052"

The above input results in the message field being assigned a LinkedHashMap with the following key-value pairs:

{8=FIX.4.4, 9=69, 35=A, 49=MBT, 56=TargetCompID, 34=9, 52=20130625-04:05:32.682, 98=0, 108=30, 10=052}

See Quoted Values for details on handling more complex cases.

Picocli 4.3 introduced the splitSynopsisLabel attribute to control what is shown in the synopsis of the usage help message. See Split Synopsis Label for details.

6.3. Arity

Sometimes you want to define an option that requires more than one option parameter for each option occurrence on the command line.

The arity attribute lets you control exactly how many parameters to consume for each option occurrence.

The arity attribute can specify an exact number of required parameters, or a range with a minimum and a maximum number of parameters. The maximum can be an exact upper bound, or it can be "*" to denote any number of parameters. For example:

Java
class ArityDemo {
    @Parameters(arity = "1..3", description = "one to three Files")
    File[] files;

    @Option(names = "-f", arity = "2", description = "exactly two floating point numbers")
    double[] doubles;

    @Option(names = "-s", arity = "1..*", description = "at least one string")
    String[] strings;
}
Kotlin
class ArityDemo {
    @Parameters(arity = "1..3", description = ["one to three Files"])
    lateinit var files: Array<File>

    @Option(names = ["-f"], arity = "2", description = ["exactly two floating point numbers"])
    lateinit var doubles: DoubleArray

    @Option(names = ["-s"], arity = "1..*", description = ["at least one string"])
    lateinit var strings: Array<String>
}

A MissingParameterException is thrown when fewer than the minimum number of parameters is specified on the command line.

Once the minimum number of parameters is consumed, picocli will check each subsequent command line argument to see whether it is an additional parameter, or a new option. For example:

ArityDemo -s A B C -f 1.0 2.0 /file1 /file2

Option -s has arity "1..*" but instead of consuming all parameters, the -f argument is recognized as a separate option.

6.4. Variable Arity Limitations

6.4.1. Variable Arity Options and Unknown Options

As mentioned in the previous section, while processing parameters for an option with variable arity, when a known option, a subcommand, or the end-of-options delimiter is encountered, picocli will stop adding parameters to the variable arity option.

However, by default the picocli parser does not give special treatment to unknown options (values that "look like" an option) when processing parameters for an option with variable arity. Such values are simply consumed by the option with variable arity. From picocli 4.4 this is configurable.

6.4.2. Variable Arity Options and Positional Parameters

Be careful when defining commands that have both an option with variable arity (like arity = "0..*") and a positional parameter.

The picocli parser is "greedy" when it handles option parameters for options with variable arity: it looks at the value following the option name, and if that value can be taken as a parameter (not another option or subcommand, and max arity is not reached yet) then it will process the value as a parameter for that option. This may not always be what you want.

For example:

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

    @Option(names = "-y", arity = "0..*",
      description = "Option with optional parameters")
    List<String> values;
}
Kotlin
class Ambiguous {
    @Parameters(description = ["The file (required)."])
    lateinit var file: File

    @Option(names = ["-y"], arity = "0..*",
      description = ["Option with optional parameters"])
    lateinit var values: List<String>
}

When -y a b c path/to/file 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 -y a b c ‐‐ VALUE, but this may not be obvious to many users. One idea is to show the end-of-options delimiter in the usage help.

A better alternative may be to redesign your command to avoid the ambiguity altogether. One idea is to use the default arity (arity = "1" in our example) and use the split attribute to allow users to specify multiple values in a single argument like a,b,c. If -y takes only a single parameter, then user input like -y a,b,c path/to/file is no longer ambiguous.

6.5. Default Arity

If no arity is specified, the number of parameters depends on the field’s type.

6.5.1. Option Arity

Table 1. Default arity for @Option fields
@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
@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;
Kotlin
@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}."])
lateinit var syslogLevel: MyLogLevel

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
class Ambiguous {
    @Parameters(description = "The file (required).")
    File file;

    @Option(names = "-x", arity = "0..1",
      description = "Option with optional parameter")
    String value;
}
Kotlin
class Ambiguous {
    @Parameters(description = ["The file (required)."])
    lateinit var file: File

    @Option(names = ["-x"], arity = "0..1",
        description = ["Option with optional parameter"])
    lateinit var value: String
}

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
class MandatoryOption {
    @Option(names = "-n", required = true, description = "mandatory number")
    int number;

    @Parameters
    File[] files;
}
Kotlin
class MandatoryOption {
    @Option(names = ["-n"], required = true, description = ["mandatory number"])
    var number = 0

    @Parameters
    lateinit var files: Array<File>
}

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
class BothOptionAndParametersMandatory {
    @Parameters(arity = "1..*", description = "at least one File")
    File[] files;

    @Option(names = "-n", required = true, description = "mandatory number")
    int number;
}
Kotlin
class BothOptionAndParametersMandatory {
    @Parameters(arity = "1..*", description = ["at least one File"])
    lateinit var files: Array<File>

    @Option(names = ["-n"], required = true, description = ["mandatory number"])
    var number = 0
}

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 example 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
@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;
    }
}
Kotlin
@Command(name = "exclusivedemo")
class MutuallyExclusiveOptionsDemo {

    @ArgGroup(exclusive = true, multiplicity = "1")
    lateinit var exclusive: Exclusive

    class Exclusive {
        @Option(names = ["-a"], required = true) var a = 0
        @Option(names = ["-b"], required = true) var b = 0
        @Option(names = ["-c"], required = true) var c = 0
    }
}

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
MutuallyExclusiveOptionsDemo 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());
}
Kotlin
val example = MutuallyExclusiveOptionsDemo()
val cmd = CommandLine(example)

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

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
@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;
    }
}
Kotlin
@Command(name = "co-occur")
class DependentOptionsDemo {

    @ArgGroup(exclusive = false)
    lateinit var dependent: Dependent

    class Dependent {
        @Option(names = ["-a"], required = true) var a = 0
        @Option(names = ["-b"], required = true) var b = 0
        @Option(names = ["-c"], required = true) var c = 0
    }
}

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
DependentOptionsDemo 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());
}
Kotlin
val example = DependentOptionsDemo()
val cmd = CommandLine(example)

try {
    cmd.parseArgs("-a=1", "-b=2")
} catch (ex: MissingParameterException) {
    assert("Error: Missing required argument(s): -c=<c>" == ex.message)
}
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
@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;
    }
}
Kotlin
@Command(name = "co-occur-with-optional-options")
class DependentWithOptionalOptionsDemo {

    @ArgGroup(exclusive = false, multiplicity = "1")
    lateinit var group: DependentWithOptionalOptions

    class DependentWithOptionalOptions {
        @Option(names = ["-a"], required = true)  var a = 0
        @Option(names = ["-b"], required = true)  var b = 0
        @Option(names = ["-c"], required = false) var c = 0
    }
}

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
@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);
    }
}
Kotlin
@Command(name = "sectiondemo", description = ["Section demo"])
class OptionSectionDemo {

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

    class Section1 {
        @Option(names = ["-a"], description = ["Option A"]) var a = 0
        @Option(names = ["-b"], description = ["Option B"]) var b = 0
        @Option(names = ["-c"], description = ["Option C"]) var c = 0
    }

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

    class Section2 {
        @Option(names = ["-x"], description = ["Option X"]) var x = 0
        @Option(names = ["-y"], description = ["Option Y"]) var y = 0
        @Option(names = ["-z"], description = ["Option Z"]) var z = 0
    }
}

fun main(args: Array<String>) {
    CommandLine(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
@ArgGroup(heading = "First%n", order = 1) Section1 section1;
@ArgGroup(heading = "Next%n",  order = 2) Section2 section2;
@ArgGroup(heading = "Last%n",  order = 3) Section3 section3;
Kotlin
@ArgGroup(heading = "First%n", order = 1) lateinit var section1: Section1
@ArgGroup(heading = "Next%n", order = 2)  lateinit var section2: Section2
@ArgGroup(heading = "Last%n", order = 3)  lateinit var 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
@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
@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;
    }
}
Kotlin
@Command(name = "repeating-composite-demo")
class CompositeGroupDemo {
    @ArgGroup(exclusive = false, multiplicity = "1..*") lateinit var composites: List<Composite>

    class Composite {
        @ArgGroup(exclusive = false, multiplicity = "0..1")
        lateinit var dependent: Dependent

        @ArgGroup(exclusive = true, multiplicity = "1")
        lateinit var exclusive: Exclusive
    }

    class Dependent {
        @Option(names = ["-a"], required = true) var a = 0
        @Option(names = ["-b"], required = true) var b = 0
        @Option(names = ["-c"], required = true) var c = 0
    }

    class Exclusive {
        @Option(names = ["-x"], required = true) var x = false
        @Option(names = ["-y"], required = true) var y = false
        @Option(names = ["-z"], required = true) var z = false
    }
}

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
CompositeGroupDemo 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;
Kotlin
val example = CompositeGroupDemo()
val cmd = CommandLine(example)

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

val c1 = example.composites[0]
assert(c1.exclusive.x)
assert(c1.dependent.a === 1)
assert(c1.dependent.b === 1)
assert(c1.dependent.c === 1)

val c2 = example.composites[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
class 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);
    }
}
Kotlin
class GoodGroup {
    @Option(names = ["-x"], defaultValue = "123", description = ["Default: \${DEFAULT-VALUE}"])
    var x = 0
}

@Command(name = "good", description = ["usage help shows the default value"])
class GoodExample {
    @ArgGroup
    lateinit var goodGroup: GoodGroup
}

fun main(args: Array<String>) {
    CommandLine(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
class 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);
    }
}
Kotlin
class BadGroup {
    @Option(names = ["-x"], description = ["Default: \${DEFAULT-VALUE}"])
    var x = 123 // value not found until `BadGroup` is instantiated
}

@Command(name = "bad", description = ["usage help shows wrong default value"])
class BadExample {
    @ArgGroup
    lateinit var badGroup: BadGroup
}

fun main(args: Array<String>) {
    CommandLine(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
class 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 :-(
}
Kotlin
class MyGroup {
    // (group options):
    // specify default values both in the annotation and in the initial value
    @Option(names = "-x", defaultValue = "XX")
    var 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
// instantiating the group in the declaration:
// all options in this group now also have their initial (default) value
@ArgGroup MyGroup myGroup = new MyGroup();
Kotlin
// instantiating the group in the declaration:
// all options in this group now also have their initial (default) value
@ArgGroup var myGroup = 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
@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...
    }
}
Kotlin
@Command(name = "group-default-demo")
class MyApp : Runnable {
    @ArgGroup lateinit var outer: Outer // no initial value

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

        @ArgGroup(exclusive = true)
        lateinit var inner: Inner // no initial value
    }

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

    override fun run() {
        if (outer == null) { // -x option was not specified on command line
            // perform any logic that needs to happen if -x is missing
            outer = 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 = 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
@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));
    }
}
Kotlin
@Command(name = "grades", mixinStandardHelpOptions = true, version = ["grades 1.0"])
class Grades : Runnable {

    class StudentGrade {
        @Parameters(index = "0") lateinit var name: String
        @Parameters(index = "1") lateinit var grade: BigDecimal
    }

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

    override fun run() {
        gradeList.forEach { e -> println("${e.name}: ${e.grade}") }
    }
}

fun main(args: Array<String>) {
    System.exit(CommandLine(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
new CommandLine(new MyApp()).execute(args);
Kotlin
CommandLine(MyApp()).execute(*args)

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

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));
    }
}
Kotlin
 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 : Callable<Int> {

    @Option(names = ["-x"])
    var x = 0

    override fun call(): Int { // business logic
        println("x=$x")
        return 123 // exit code
    }
}

fun main(args: Array<String>) { // bootstrap the application
    exitProcess(CommandLine(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 code 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
public static void main(String... args) {
  int exitCode = new CommandLine(new MyApp()).execute(args);
  System.exit(exitCode);
}
Kotlin
fun main(args: Array<String>) {
    val exitCode = CommandLine(MyApp()).execute(*args)
    exitProcess(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
@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");
Kotlin
@Command(name = "greet")
class Greet : Callable<Int> {
    override fun call(): Int {
        println("hi")
        return 1
    }

    // define a "shout" subcommand with a @Command-annotated method
    @Command
    fun shout(): Int {
        println("HI!")
        return 2
    }
}

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

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

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();
Kotlin
@Command(name = "wave")
class Gesture : Runnable, IExitCodeGenerator {

    override fun run() {
        println("wave")
    }

    override fun getExitCode(): Int {
        return 3
    }
}

assert(3 == CommandLine(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 answer). This can be customized with the @Command annotation. For example:

@Command(exitCodeOnInvalidInput = 123,
   exitCodeOnExecutionException = 456)

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

Java
class MyMapper implements IExitCodeExceptionMapper {
    @Override
    public int getExitCode(Throwable t) {
        if (t instanceof FileNotFoundException) {
            return 74;
        }
        return 1;
    }
}
Kotlin
class MyMapper : IExitCodeExceptionMapper {
    override fun getExitCode(t: Throwable): Int {
        return if (t is FileNotFoundException) {
            74
        } else 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
PrintStream 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);
Kotlin
val out: PrintStream = // output stream for user-requested help
val err: PrintStream = // output stream for error messages
val ansi: Ansi = // to use ANSI colors and styles or not
CommandLine.run(MyRunnable(), out, err, ansi, *args)

After:

Java
PrintWriter 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);
Kotlin
var out: PrintWriter = // output stream for user-requested help
var err: PrintWriter = // output stream for error messages
val ansi: Ansi = // to use ANSI colors and styles or not

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

val exitCode = cmd.execute(*args)

9.6.2. Return Value from Callable or Method

Before:

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

MyResult result = CommandLine.call(new MyCallable(), args);
Kotlin
class MyCallable : Callable<MyResult> {
    override fun call(): MyResult { /* ... */ }
}

val result: MyResult = CommandLine.call(MyCallable(), *args)

After:

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

9.6.3. Invoking Command Methods

Before:

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

MyResult result = CommandLine.invoke("doit", MyCommand.class, args);
Kotlin
class MyCommand {
    @Command
    fun doit(@Option(names = ["-x"]) x: Int) : MyResult{ /* ... */ }
}

val result = CommandLine.invoke("doit", MyCommand::class.java, *args) as MyResult

After:

Java
Method doit = CommandLine.getCommandMethods(MyCommand.class, "doit").get(0);
CommandLine cmd = new CommandLine(doit);
int exitCode = cmd.execute(args);
MyResult result = cmd.getExecutionResult();
Kotlin
val doit: Method = CommandLine.getCommandMethods(MyCommand::class.java, "doit")[0]
val cmd = CommandLine(doit)
val exitCode = cmd.execute(*args)
val result: MyResult = 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
CommandLine cmd = new CommandLine(MyTopLevelCommand())
        .addSubcommand("status",   new GitStatus())
        .addSubcommand("commit",   new GitCommit())
        .addSubcommand("add",      new GitAdd());
List<Object> result = cmd.parseWithHandler(new RunAll(), args);
Kotlin
val cmd = CommandLine(MyTopLevelCommand())
        .addSubcommand("status",   GitStatus())
        .addSubcommand("commit",   GitCommit())
        .addSubcommand("add",      GitAdd())
val result = cmd.parseWithHandler<List<Any>>(CommandLine.RunAll(), args)}

After

Java
CommandLine 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);
Kotlin
val cmd = CommandLine(MyTopLevelCommand())
        .addSubcommand("status",   GitStatus())
        .addSubcommand("commit",   GitCommit())
        .addSubcommand("add",      GitAdd())

// the default is RunLast, this can be customized:
cmd.executionStrategy = RunAll()
val exitCode = cmd.execute(*args)

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

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();
}
Kotlin
// getting return value from Callable or Method command
val topResult: Int = cmd.getExecutionResult()

// getting return value from Callable or Method subcommand
val parseResult = cmd.parseResult
if (parseResult.subcommand() != null) {
    val sub: CommandLine = parseResult.subcommand().commandSpec().commandLine()
    val subResult = sub.getExecutionResult<Int>()
}

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
Callable<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();
}
Kotlin
val callable: Callable<Int> = MyCallable()
val cmd = CommandLine(callable)
return try {
    val parseResult = cmd.parseArgs(*args)

    // Did user request usage help (--help)?
    if (cmd.isUsageHelpRequested) {
        cmd.usage(cmd.out)
        cmd.commandSpec.exitCodeOnUsageHelp()

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

    // invalid user input: print error message and usage help
} catch (ex: ParameterException) {
    cmd.err.println(ex.message)
    if (!UnmatchedArgumentException.printSuggestions(ex, cmd.err)) {
        ex.commandLine.usage(cmd.err)
    }
    cmd.commandSpec.exitCodeOnInvalidInput()

    // exception occurred in business logic
} catch (ex: Exception) {
    ex.printStackTrace(cmd.err)
    cmd.commandSpec.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 alternatives 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
new CommandLine(new MyApp())
    .setParameterExceptionHandler(new ShortErrorMessageHandler())
    .execute(args);
Kotlin
CommandLine(MyApp())
    .setParameterExceptionHandler(ShortErrorMessageHandler())
    .execute(*args)

Where the IParameterExceptionHandler implementation could be something like this:

Java
class 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();
    }
}
Kotlin
class ShortErrorMessageHandler : IParameterExceptionHandler {

    override fun handleParseException(ex: ParameterException, args: Array<String>): Int {
        val cmd = ex.commandLine
        val err = cmd.err

        // if tracing at DEBUG level, show the location of the issue
        if ("DEBUG".equals(System.getProperty("picocli.trace"), ignoreCase = true)) {
            err.println(cmd.colorScheme.stackTraceText(ex))
        }

        err.println(cmd.colorScheme.errorText(ex.message)) // bold red
        UnmatchedArgumentException.printSuggestions(ex, err)
        err.print(cmd.help.fullSynopsis())

        val spec = cmd.commandSpec
        err.print("Try '${spec.qualifiedName()} --help' for more information.%n")

        return if (cmd.exitCodeExceptionMapper != null)
            cmd.exitCodeExceptionMapper.getExitCode(ex)
            else 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
new CommandLine(new MyApp())
    .setExecutionExceptionHandler(new PrintExceptionMessageHandler())
    .execute(args);
Kotlin
CommandLine(MyApp())
    .setExecutionExceptionHandler(PrintExceptionMessageHandler())
    .execute(*args)

Where the IExecutionExceptionHandler implementation could look something like this:

Java
class 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();
    }
}
Kotlin
class PrintExceptionMessageHandler : IExecutionExceptionHandler {
    override fun handleExecutionException(ex: Exception,
                                          cmd: CommandLine,
                                          parseResult: ParseResult): Int {

        // bold red error message
        cmd.err.println(cmd.colorScheme.errorText(ex.message))

        return if (cmd.exitCodeExceptionMapper != null)
            cmd.exitCodeExceptionMapper.getExitCode(ex)
            else cmd.commandSpec.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
MyApp app = new MyApp();
new CommandLine(app).parseArgs("--some --options and parameters".split(" "));
assertTrue(app.some);
Groovy
MyApp app = new MyApp()
new CommandLine(app).parseArgs('--some --options and parameters'.split(' '))
assert 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.

An common use case is when your application is called as part of the build. For example, Maven provides the exec-maven-plugin with exec:java goal, and Gradle similarly provides the Exec and JavaExec tasks.

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
public 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
public 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
class 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;
    }
    // ...
}
Kotlin
class SingleOptionValidationExample {
    private var prime = 0

    @Spec lateinit var spec : CommandSpec // injected by picocli

    @Option(names = ["-p", "--prime"], paramLabel = "NUMBER")
    fun setPrimeNumber(value: Int) {
        var invalid = false
        for (i in 2..value / 2) {
            if (value % i == 0) {
                invalid = true
                break
            }
        }
        if (invalid) {
            throw 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
@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();
    }
}
Kotlin
@Command(name = "myapp", mixinStandardHelpOptions = true, version = ["myapp 0.1"])
class MultiOptionValidationExample : Runnable {
    @Option(names = ["--xml"])  var xmlFiles: List<File>? = null
    @Option(names = ["--csv"])  var csvFiles: List<File>? = null
    @Option(names = ["--json"]) var jsonFiles: List<File>? = null

    @Spec lateinit var spec : CommandSpec // injected by picocli

     override fun run() {
        validate()

        // remaining business logic here
    }

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

    private fun missing(list: List<*>?): Boolean {
        return list == null || list.isEmpty()
    }
}

fun main(args: Array<String>) {
    exitProcess(CommandLine(MultiOptionValidationExample()).execute(*args))
}

10.2.3. JSR-380 BeanValidation

If you want to keep your validation declarative and annotation-based, take a look at JSR 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.

The picocli wiki has a full example, below is a snippet:

Java
import 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);
        }
    }
}
Kotlin
import 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.*

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

    @NotNull(message = "Name cannot be null")
    @CommandLine.Option(names = ["-n", "--name"], description = ["mandatory"])
    private lateinit var name: String

    @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 var age = 0

    @Email(message = "Email should be valid")
    @Option(names = ["-e", "--email"], description = ["valid email"])
    private lateinit var email: String

    @Spec
    lateinit var spec: CommandSpec
    override fun toString(): String {
        return "User{name=$name, age=$age, email=$email}"
    }

    override fun run() {
        validate()

        // remaining business logic here
    }

    private fun validate() {
        val validator: Validator = Validation.buildDefaultValidatorFactory().validator
        val violations: Set<ConstraintViolation<User>> = validator.validate(this)
        if (violations.isNotEmpty()) {
            var errorMsg = ""
            for (violation in violations) {
                errorMsg += "ERROR: ${violation.message}\n"
            }
            throw CommandLine.ParameterException(spec.commandLine(), errorMsg)
        }
    }
}

fun main(args: Array<String>) {
    CommandLine(User()).execute(*args)
}

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
class 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);
        }
    }
}
Kotlin
class ValidatingExecutionStrategy : IExecutionStrategy {
    override fun execute(parseResult : ParseResult) : Int {
        validate(parseResult.commandSpec())
        return CommandLine.RunLast().execute(parseResult) // default execution strategy
    }

    private fun validate(spec : CommandSpec) {
        val validator: Validator = Validation.buildDefaultValidatorFactory().validator
        val violations: Set<ConstraintViolation<Any>> = validator.validate(spec.userObject())
        if (violations.isNotEmpty()) {
            var errorMsg = ""
            for (violation in violations) {
                errorMsg += "ERROR: ${violation.message}\n"
            }
            throw CommandLine.ParameterException(spec.commandLine(), errorMsg)
        }
    }
}

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

Java
    public static void main(String... args) {
        new CommandLine(new MyApp())
                .setExecutionStrategy(new ValidatingExecutionStrategy())
                .execute(args);
    }
Kotlin
    public static void main(String... args) {
        CommandLine(MyApp())
                .setExecutionStrategy(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.

See the casesensitivity package in picocli-examples for some examples.

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
class 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;
Kotlin
class AbbreviationsAndPosix {
    @Option(names = ["-A"])       var a = false
    @Option(names = ["-B"])       var b = false
    @Option(names = ["-AaaBbb"])  var aaaBbb = false
}

val app = AbbreviationsAndPosix()
CommandLine(app).setAbbreviatedOptionsAllowed(true).parseArgs("-AB")
assertTrue(app.aaaBbb) // the long option is matched from abbreviated input -AB
assertFalse(app.a)
assertFalse(app.b)

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
@Option(names = "-p") int port;
Kotlin
@Option(names = ["-p"]) var port = 0

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
class OnlyThree {
    @Parameters(arity = "3") String[] values;
}
Kotlin
class OnlyThree {
    @Parameters(arity = "3") lateinit var values: Array<String>
}

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
@Option(names = "-x") String x;
@Option(names = "-y") String y;
@Parameters String[] remainder;
Kotlin
@Option(names = ["-x"]) lateinit var x: String
@Option(names = ["-y"]) lateinit var y: String
@Parameters lateinit var remainder: Array<String>

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
class MyApp {
    @Option(names = "-x") String x;
}
Kotlin
class MyApp {
    @Option(names = ["-x"]) lateinit var x: String
}

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

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

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

Java
new CommandLine(new MyApp())
        .setUnmatchedOptionsAllowedAsOptionParameters(false)
        .parseArgs("-x", "-z");
Kotlin
CommandLine(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
class 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);
    }
}
Kotlin
class App {
    @Option(names = ["-x"]) var x: String? = null
    @Option(names = ["-y"]) var y: String? = null
}

fun main(args: Array<String>) {
    val app = App()
    CommandLine(app).setTrimQuotes(true).parseArgs(*args)
    println("x='${app.x}', y='${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:

java App -x=\"-y\"

This will print the following output:

x='-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
class 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);
    }
}
Kotlin
class App : Runnable {
    @Option(names = ["-x"], parameterConsumer = CustomConsumer::class)
    var x: String? = null

    @Option(names = ["-y"])
    var y: String? = null

    @Command
    fun mySubcommand() {}

    internal class CustomConsumer : IParameterConsumer {
        override fun consumeParameters(args: Stack<String>, argSpec: ArgSpec, cmdSpec: CommandSpec) {
            if (args.isEmpty()) {
                throw ParameterException(cmdSpec.commandLine(),
                        "Error: option '-x' requires a parameter"
                )
            }
            val arg = args.pop()
            argSpec.setValue(arg)
        }
    }

    override fun run() {
        System.out.printf("x='%s', y='%s'%n", x, y)
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            CommandLine(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:

java 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
@Option(names = "-x", split = ",")
String[] parts;
Kotlin
@Option(names = ["-x"], split = ",")
lateinit var parts: Array<String>

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:

interface 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 expressions is used to control the above. Use CommandLine::setNegatableOptionTransformer to replace the 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 find command has a -exec 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
@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;
            }
        }
    }
}
Kotlin
@Command(name = "find")
class Find {
    @Option(names = ["-exec"], parameterConsumer = ExecParameterConsumer::class)
    var list: List<String> = ArrayList()
}

class ExecParameterConsumer : IParameterConsumer {
    override fun consumeParameters(args: Stack<String>, argSpec: ArgSpec, commandSpec: CommandSpec) {
        val list = argSpec.getValue<MutableList<String>>()
        while (!args.isEmpty()) {
            val 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
@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
        }
    }
}
Kotlin
class Edit : Runnable {

    @Parameters(index = "0", arity = "0..1", description = ["The file to edit."])
    var file: File? = null

    enum class Editor { defaultEditor, eclipse, idea, netbeans }

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

    class MyPreprocessor : IParameterPreprocessor {
        override fun preprocess(args: Stack<String>,
                                commandSpec: CommandSpec,
                                argSpec: ArgSpec,
                                info: Map<String, Any>): Boolean {
            // we need to decide whether the next arg is the file to edit
            // or the name of the editor to use ...
            if (" " == info["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
@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;
Kotlin
@Option(names = ["-V", "--version"], versionHelp = true, description = ["display version info"])
var versionInfoRequested = false

@Option(names = ["-h", "--help"], usageHelp = true, description = ["display this help message"])
var usageHelpRequested = false

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

Java
App app = CommandLine.populateCommand(new App(), args);
if (app.usageHelpRequested) {
    CommandLine.usage(new App(), System.out);
    return;
}
Kotlin
val app: App = CommandLine.populateCommand(App(), *args)
if (app.usageHelpRequested) {
    CommandLine.usage(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
CommandLine 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
Kotlin
val commandLine = CommandLine(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
@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() { /* ... */ }
}
Kotlin
@Command(mixinStandardHelpOptions = true, version = ["auto help demo - picocli 3.0"])
class AutoHelpDemo : Runnable {

    @Option(names = ["--option"], description = ["Some option."])
    lateinit var option: String

    override fun 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
import picocli.CommandLine.HelpCommand;

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

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

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

myapp help subcommand

To print usage help for the main command:

myapp 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.

@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.

public 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
@Command(version = "1.0")
class VersionedCommand {
    @Option(names = { "-V", "--version" }, versionHelp = true,
            description = "print version information and exit")
    boolean versionRequested;
    /* ... */ }
Kotlin
@Command(version = ["1.0"])
class VersionedCommand {
    @Option(names = ["-V", "--version"], versionHelp = true,
            description = ["print version information and exit"])
    var versionRequested = false
    /* ... */ }

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

Java
CommandLine commandLine = new CommandLine(new VersionedCommand());
commandLine.parseArgs(args);
if (commandLine.isVersionHelpRequested()) {
    commandLine.printVersionHelp(System.out);
    return;
}
Kotlin
val commandLine = CommandLine(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
@Command(version = { "Versioned Command 1.0", "Build 12345", "(c) 2017" })
class VersionedCommand { /* ... */ }
Kotlin
@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
@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 { /* ... */ }
Kotlin
@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
@Command(version = {
        "@|yellow Versioned Command 1.0|@",
        "@|blue Build 12345|@",
        "@|red,bg(white) (c) 2017|@" })
class VersionedCommand { /* ... */ }
Kotlin
@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.

Screenshot of version information containing markup with Ansi styles and colors

13.1.5. Version Info With Format Specifiers

As of picocli 1.0, the version may contain format specifiers:

Java
@Command(version = {
    "Versioned Command 1.0",
    "Build %1$s",
    "(c) 2017, licensed to %2$s" })
class VersionedCommand { /* ... */ }
Kotlin
@Command(version = [
        "Versioned Command 1.0",
        "Build %1\$s",
        "(c) 2017, licensed to %2\$s"])
class VersionedCommand { /* ... */ }

Format argument values can be passed to the printVersionHelp method:

Java
String[] args = {"1234", System.getProperty("user.name")};
new CommandLine(new VersionedCommand())
    .printVersionHelp(System.out, Help.Ansi.AUTO, args);
Kotlin
val args = arrayOf("1234", System.getProperty("user.name"))
CommandLine(VersionedCommand()).printVersionHelp(System.out, Help.Ansi.AUTO, *args)

13.2. Dynamic Version Information

13.2.1. Command versionProvider Attribute

As of picocli 2.2, the @Command annotation supports a versionProvider attribute. Applications may specify a IVersionProvider implementation in this attribute, and picocli will instantiate this class and invoke it to collect version information.

Java
@Command(versionProvider = com.my.custom.ManifestVersionProvider.class)
class App { /* ... */ }
Kotlin
@Command(versionProvider = com.my.custom.ManifestVersionProvider::class)
class App { /* ... */ }

This is useful when the version of an application should be detected dynamically at runtime. For example, an implementation may return version information obtained from the JAR manifest, a properties file or some other source.

13.2.2. IVersionProvider Interface

Custom version providers need to implement the picocli.CommandLine.IVersionProvider interface:

public interface IVersionProvider {
    /**
     * Returns version information for a command.
     * @return version information (each string in the array is displayed on a separate line)
     * @throws Exception an exception detailing what went wrong when obtaining version information
     */
    String[] getVersion() throws Exception;
}

Version providers declared with the versionProvider attribute need to have a public no-argument constructor to be instantiated, unless a Custom Factory is installed to instantiate classes.

The GitHub project has a manifest file-based example and a build-generated version properties file-based example version provider implementation.

13.2.3. Dynamic Version Info with Variables

The version strings returned from the IVersionProvider may contain system properties and environment variables.

For example:

Java
class VersionProviderWithVariables implements IVersionProvider {
    public String[] getVersion() {
        return new String[] { "${COMMAND-FULL-NAME} version 1.0" };
    }
}
Kotlin
class VersionProviderWithVariables : IVersionProvider {
    override fun getVersion(): Array<String> {
        return arrayOf("\${COMMAND-FULL-NAME} version 1.0")
    }
}

The above example version provider will show the fully qualified command name (that is, preceded by its parent fully qualified command name) of any command that uses this version provider.

This is one way to create a version provider that can be reused across multiple commands.

13.2.4. Injecting CommandSpec Into an IVersionProvider

As of picocli 4.2.0, IVersionProvider implementations can have @Spec-annotated fields. If such a field exists, picocli will inject the CommandSpec of the command that uses this version provider. This gives the version provider access to the full command hierarchy, and may make it easier to implement version providers that can be reused among multiple commands.

For example:

Java
class MyVersionProvider implements IVersionProvider {
    @Spec CommandSpec spec;

    public String[] getVersion() {
        return new String[] { "Version info for " + spec.qualifiedName() };
    }
}
Kotlin
class MyVersionProvider : IVersionProvider {
    @Spec
    lateinit var spec: CommandSpec

    override fun getVersion(): Array<String> {
        return arrayOf("Version info for " + spec.qualifiedName())
    }
}

14. Usage Help

14.1. Compact Example

A default picocli usage help message looks like this:

Usage: cat [-AbeEnstTuv] [--help] [--version] [FILE...]
Concatenate FILE(s), or standard input, to standard output.
      FILE                 Files whose contents to display
  -A, --show-all           equivalent to -vET
  -b, --number-nonblank    number nonempty output lines, overrides -n
  -e                       equivalent to -vE
  -E, --show-ends          display $ at end of each line
  -n, --number             number all output lines
  -s, --squeeze-blank      suppress repeated empty output lines
  -t                       equivalent to -vT
  -T, --show-tabs          display TAB characters as ^I
  -u                       (ignored)
  -v, --show-nonprinting   use ^ and M- notation, except for LFD and TAB
      --help               display this help and exit
      --version            output version information and exit
Copyright(c) 2017

The usage help message is generated from annotation attributes, like below:

Java
@Command(name = "cat", footer = "Copyright(c) 2017",
         description = "Concatenate FILE(s), or standard input, to standard output.")
class Cat {

    @Parameters(paramLabel = "FILE", description = "Files whose contents to display")
    List<File> files;

    @Option(names = "--help", usageHelp = true, description = "display this help and exit")
    boolean help;

    @Option(names = "-t",                 description = "equivalent to -vT")  boolean t;
    @Option(names = "-e",                 description = "equivalent to -vE")  boolean e;
    @Option(names = {"-A", "--show-all"}, description = "equivalent to -vET") boolean all;

    // ...
}
Kotlin
@Command(name = "cat", footer = ["Copyright(c) 2017"],
         description = ["Concatenate FILE(s), or standard input, to standard output."])
class Cat {

    @Parameters(paramLabel = "FILE", description = ["Files whose contents to display"])
    lateinit var files: List<File>

    @Option(names = ["--help"], usageHelp = true, description = ["display this help and exit"])
    var help = false

    @Option(names = ["-t"],               description = ["equivalent to -vT"])  var t = false
    @Option(names = ["-e"],               description = ["equivalent to -vE"])  var e = false
    @Option(names = ["-A", "--show-all"], description = ["equivalent to -vET"]) var all = false

    // ...
}

14.2. Command Name

In the above example, the program name is taken from the name attribute of the Command annotation:

@Command(name = "cat")

Without a name attribute, picocli will show a generic <main class> in the synopsis:

Usage: <main class> [-AbeEnstTuv] [--help] [--version] [FILE...]

14.3. Parameter Labels

Non-boolean options require a value. The usage help should explain this, and picocli shows the option parameter in the synopsis and in the option list. By default, the field name is shown in < and > fish brackets. Use the paramLabel attribute to display a different name. For example:

Usage: <main class> [-f=FILE] [-n=<number>] NUM <host>
      NUM        number param
      host       the host parameter
  -f= FILE       a file
  -n= <number>   a number option

Some annotated fields in the below example class have a paramLabel attribute and others don’t:

Java
@Command()
class ParamLabels {
    @Option(names = "-f",    description = "a file",       paramLabel = "FILE") File f;
    @Option(names = "-n",    description = "a number option")                   int number;
    @Parameters(index = "0", description = "number param", paramLabel = "NUM")  int n;
    @Parameters(index = "1", description = "the host parameter")                InetAddress host;
}
Kotlin
@Command
class ParamLabels {
    @Option(names = ["-f"], description = ["a file"], paramLabel = "FILE")
    lateinit var f: File

    @Option(names = ["-n"], description = ["a number option"])
    var number = 0

    @Parameters(index = "0", description = ["number param"], paramLabel = "NUM")
    var n = 0

    @Parameters(index = "1", description = ["the host parameter"])
    lateinit var host: InetAddress
}
For demonstration purposes the above example mixes the all-uppercase (e.g., NUM) style label and the fish bracket (e.g., <number>) style labels. For real applications, mixing these label styles should be avoided. An application should consistently use only one style.

14.4. Option List

14.4.1. Sorted Option List

By default, the options list in the usage help message displays options in alphabetical order. Use the sortOptions = false attribute to display options in the order they are declared in your class.

@Command(sortOptions = false)
Sorting on declaration order is done on a best effort basis, see the Reordering Options section below.

14.4.2. Reordering Options

Note that picocli cannot reliably detect declaration order in commands that have both @Option-annotated methods and @Option-annotated fields.

The @Option(order = <int>) attribute can be used to explicitly control the position in the usage help message at which the option should be shown. Options with a lower number are shown before options with a higher number.

14.4.3. Sorted Synopsis

By default, the synopsis of the usage help message displays options in alphabetical order. Picocli 4.7.0 introduced a sortSynopsis = false attribute to let the synopsis display options in the order they are declared in your class, or sorted by their order attribute.

@Command(sortSynopsis = false)
Regardless of the sorting strategy, boolean short options are shown first as a single clustered group in the synopsis, followed by options that take parameters, unless the parser is configured to disallow clustered boolean short options.

14.4.4. Required-Option Marker

Required options can be marked in the option list by the character specified with the requiredOptionMarker attribute. By default options are not marked because the synopsis shows users which options are required and which are optional. This feature may be useful in combination with the abbreviateSynopsis attribute. For example:

Java
@Command(requiredOptionMarker = '*', abbreviateSynopsis = true)
class Example {
    @Option(names = {"-a", "--alpha"}, description = "optional alpha") String alpha;
    @Option(names = {"-b", "--beta"}, required = true, description = "mandatory beta") String beta;
}
Kotlin
@Command(requiredOptionMarker = '*', abbreviateSynopsis = true)
class Example {
    @Option(names = ["-a", "--alpha"], description = ["optional alpha"])
    lateinit var alpha: String

    @Option(names = ["-b", "--beta"], required = true, description = ["mandatory beta"])
    lateinit var beta: String
}

Produces the following usage help message:

Usage: <main class> [OPTIONS]
  -a, --alpha=<alpha>   optional alpha
* -b, --beta=<beta>     mandatory beta

14.4.5. Short and Long Option Columns

The default layout shows short options and long options in separate columns, followed by the description column.

Only POSIX short options are shown in the first column, that is, options that start with a single - dash and are one character long.

Options with two characters are not considered short options and are shown in the long option column.

This is a common layout for Unix utilities, and can enhance usability: applications can give a subtle hint to end users that an option is common and encouraged by providing both a short and a long name for the option. Conversely, the absence of a short option can signal that the option is unusual or perhaps should be used with care.

It is possible to left-align all options by using a custom layout. See LeftAlignOptions.java in the picocli-examples module for an example.

14.4.6. Long Option Column Width

The default layout shows short options and long options in separate columns, followed by the description column. The width of the long options column shrinks automatically if all long options are very short, but by default this column does not grow larger than 20 characters.

If the long option with its option parameter is longer than 20 characters (for example: --output=<outputFolder>), the long option overflows into the description column, and the option description is shown on the next line.

This (the default) looks like this:

Usage: myapp [-hV] [-o=<outputFolder>]
  -h, --help      Show this help message and exit.
  -o, --output=<outputFolder>
                  Output location full path.
  -V, --version   Print version information and exit.

As of picocli 4.2, there is programmatic API to change this via the CommandLine::setUsageHelpLongOptionsMaxWidth and UsageMessageSpec::longOptionsMaxWidth methods.

In the above example, if we call commandLine.setUsageHelpLongOptionsMaxWidth(23) before printing the usage help, we get this result:

Usage: myapp [-hV] [-o=<outputFolder>]
  -h, --help                    Show this help message and exit.
  -o, --output=<outputFolder>   Output location full path.
  -V, --version                 Print version information and exit.

The minimum value that can be specified for longOptionsMaxWidth is 20, the maximum value is the usage width minus 20.

14.5. Usage Width

The default width of the usage help message is 80 characters. Commands defined with @Command(usageHelpWidth = NUMBER) in the annotations will use the specified width.

Picocli 3.0 also introduced programmatic API for this via the CommandLine::setUsageHelpWidth and UsageMessageSpec::width methods.

End users can use the system property picocli.usage.width to specify a custom width that overrides the programmatically set value.

The minimum width that can be configured is 55 characters.

14.6. Auto (Terminal) Width

As of picocli 4.0, commands defined with @Command(usageHelpAutoWidth = true) will try to adjust the usage message help layout to the terminal width. There is also programmatic API to control this via the CommandLine::setUsageHelpAutoWidth and UsageMessageSpec::autoWidth methods.

End users may enable this by setting the system property picocli.usage.width to AUTO, and may disable this by setting this system property to a numeric value.

This feature requires Java 7.

14.7. Split Synopsis Label

Options and parameters may have a split attribute to split each parameter into smaller substrings. Regular expressions may contain literal text, but may also contain special characters. The regular expression in the split attribute may be quite different from what end users need to type.

The example below uses the plus (+) character as a separator. The regular expression in the split attribute uses backslash (\) characters to make sure that the plus character is treated as a literal.

Java
@Parameters(split = "\\+", splitSynopsisLabel = "+", paramLabel = "VALUE")
List<String> values;
Kotlin
@Parameters(split = "\\+", splitSynopsisLabel = "+", paramLabel = "VALUE")
lateinit var values: List<String>

However, end users can type simply A+B+C (using the plus character separator), not A\+B\+C. We want the usage help message to show this.

The splitSynopsisLabel attribute, introduced in picocli 4.3, controls what is shown in the synopsis of the usage help message.

With the example above, the synopsis of the usage help looks like this:

Usage: cmd [VALUE[+VALUE...]...]

14.8. Abbreviated Synopsis

If a command is very complex and has many options, it is sometimes desirable to suppress details from the synopsis with the abbreviateSynopsis attribute. For example:

Usage: <main class> [OPTIONS] [<files>...]

Note that the positional parameters are not abbreviated.

Java
@Command(abbreviateSynopsis = true)
class App {
    @Parameters File[] files;
    @Option(names = {"--count", "-c"}) int count;
    // ...
}
Kotlin
@Command(abbreviateSynopsis = true)
class App {
    @Parameters lateinit var files: Array<File>
    @Option(names = ["--count", "-c"]) var count = 0
    // ...
}

14.9. Custom Synopsis

For even more control of the synopsis, use the customSynopsis attribute to specify one or more synopsis lines. For example:

Usage: ln [OPTION]... [-T] TARGET LINK_NAME   (1st form)
  or:  ln [OPTION]... TARGET                  (2nd form)
  or:  ln [OPTION]... TARGET... DIRECTORY     (3rd form)
  or:  ln [OPTION]... -t DIRECTORY TARGET...  (4th form)

To produce a synopsis like the above, specify the literal text in the customSynopsis attribute:

Java
@Command(synopsisHeading = "", customSynopsis = {
        "Usage: ln [OPTION]... [-T] TARGET LINK_NAME   (1st form)",
        "  or:  ln [OPTION]... TARGET                  (2nd form)",
        "  or:  ln [OPTION]... TARGET... DIRECTORY     (3rd form)",
        "  or:  ln [OPTION]... -t DIRECTORY TARGET...  (4th form)",
})
class Ln { /* ... */ }
Kotlin
@Command(synopsisHeading = "", mixinStandardHelpOptions = true, customSynopsis = ["""
Usage: ln [OPTION]... [-T] TARGET LINK_NAME   (1st form)
   or: ln [OPTION]... TARGET                  (2nd form)
   or: ln [OPTION]... TARGET... DIRECTORY     (3rd form)
   or:  ln [OPTION]... -t DIRECTORY TARGET...  (4th form)"""])
class Ln { /* ... */ }

14.10. Synopsis Subcommand Label

For commands with subcommands, the string [COMMAND] is appended to the end of the synopsis (whether the synopsis is abbreviated or not). This looks something like this:

Usage: <cmd> [OPTIONS] FILES [COMMAND]

As of picocli 4.0, this can be customized with the synopsisSubcommandLabel attribute.

For example, to clarify that a subcommand is mandatory, an application may specify COMMAND, without the [ and ] brackets:

@Command(name = "git", synopsisSubcommandLabel = "COMMAND")
class Git { /* ... */ }

An application with a limited number of subcommands may want to show them all in the synopsis, for example:

Java
@Command(name = "fs", synopsisSubcommandLabel = "(list | add | delete)",
         subcommands = {List.class, Add.class, Delete.class}, mixinStandardHelpOptions = true)
class Fs { /* ... */ }
Kotlin
@Command(name = "fs", synopsisSubcommandLabel = "(list | add | delete)",
         subcommands = [List::class, Add::class, Delete::class],
         mixinStandardHelpOptions = true)
class Fs { /* ... */ }

This will show the following synopsis:

Usage: fs [-hV] (list | add | delete)

The header will be shown at the top of the usage help message (before the synopsis). The first header line is also the line shown in the subcommand list if your command has subcommands (see Usage Help for Subcommands).

Use the footer attribute to specify one or more lines to show below the generated usage help message.

Each element of the attribute String array is displayed on a separate line.

14.12. Exit Code List

By default, the usage help message does not display exit code information. Applications that call System.exit need to configure the exitCodeListHeading and exitCodeList annotation attributes. For example:

Java
@Command(exitCodeListHeading = "Exit Codes:%n",
         exitCodeList = {
             " 0:Successful program execution",
             "64:Usage error: user input for the command was incorrect, " +
                     "e.g., the wrong number of arguments, a bad flag, " +
                     "a bad syntax in a parameter, etc.",
             "70:Internal software error: an exception occurred when invoking " +
                     "the business logic of this command."})
class App {}
new CommandLine(new App()).usage(System.out);
Kotlin
@Command(exitCodeListHeading = "Exit Codes:%n",
         exitCodeList = [
             " 0:Successful program execution",
             "64:Usage error: user input for the command was incorrect,"  +
                    "e.g., the wrong number of arguments, a bad flag, " +
                    "a bad syntax in a parameter, etc.",
             "70:Internal software error: an exception occurred when invoking " +
                    "the business logic of this command."])
class App {}
fun main(args: Array<String>) : Unit = CommandLine(App()).usage(System.out)

This will print the following usage help message to the console:

Usage: <main class>
Exit Codes:
   0   Successful program execution
  64   Usage error: user input for the command was incorrect, e.g., the wrong
         number of arguments, a bad flag, a bad syntax in a parameter, etc.
  70   Internal software error: an exception occurred when invoking the
         business logic of this command.

14.13. Format Specifiers

All usage help message elements can have embedded line separator (%n) format specifiers. These are converted to the platform-specific line separator when the usage help message is printed.

Version help may have format specifiers that format additional arguments passed to the printVersionHelp method.

See the java.util.Formatter JavaDoc for details.

Note that to show percent '%' characters in the usage help message, they need to be escaped with another %. For example: @Parameters(description = "%%-age of the total") is rendered as %-age of the total.

An alternative way to control the layout of the usage help message is that some sections (header, footer, and description) can be specified as an array of Strings, where each element of the array is displayed on a separate line in the usage help message.

14.14. Section Headings

Section headers can be used to make usage message layout appear more spacious. The example below demonstrates the use of embedded line separator (%n) format specifiers:

Java
@Command(name = "commit",
         sortOptions = false,
         headerHeading = "Usage:%n%n",
         synopsisHeading = "%n",
         descriptionHeading = "%nDescription:%n%n",
         parameterListHeading = "%nParameters:%n",
         optionListHeading = "%nOptions:%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 { /* ... */ }
Kotlin
@Command(name = "commit",
         sortOptions = false,
         headerHeading = "Usage:%n%n",
         synopsisHeading = "%n",
         descriptionHeading = "%nDescription:%n%n",
         parameterListHeading = "%nParameters:%n",
         optionListHeading = "%nOptions:%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 { /* ... */ }

The usage help message generated from this class is shown below in Expanded Example.

14.15. Expanded Example

The below example demonstrates what a customized usage message can look like. Note how section headings with line separators can create a more spacious usage message, and also that options are listed in declaration order (instead of in alphabetic order).

Usage:

Record changes to the repository.

git commit [-ap] [--fixup=<commit>] [--squash=<commit>] [-c=<commit>]
           [-C=<commit>] [-F=<file>] [-m[=<msg>...]] [<files>...]

Description:

Stores the current contents of the index in a new commit along with a log
message from the user describing the changes.

Parameters:
      <files>                 the files to commit

Options:
  -a, --all                   Tell the command to automatically stage files
                                that have been modified and deleted, but new
                                files you have not told Git about are not
                                affected.
  -p, --patch                 Use the interactive patch selection interface to
                                chose which changes to commit
  -C, --reuse-message=<commit>
                              Take an existing commit object, and reuse the log
                                message and the authorship information
                                (including the timestamp) when creating the
                                commit.
  -c, --reedit-message=<commit>
                              Like -C, but with -c the editor is invoked, so
                                that the user can further edit the commit
                                message.
      --fixup=<commit>        Construct a commit message for use with rebase
                                --autosquash.
      --squash=<commit>        Construct a commit message for use with rebase
                                --autosquash. The commit message subject line is
                                taken from the specified commit with a prefix
                                of "squash! ". Can be used with additional
                                commit message options (-m/-c/-C/-F).
  -F, --file=<file>           Take the commit message from the given file. Use
                                - to read the message from the standard input.
  -m, --message[=<msg>...]     Use the given <msg> as the commit message. If
                                multiple -m options are given, their values are
                                concatenated as separate paragraphs.

The annotated class that this usage help message is generated from is shown in Section Headings.

14.16. Option-Parameter Separators

The separator displayed between options and option parameters (= by default) in the synopsis and the option list can be configured with the separator attribute.

@Command(separator = " ")
The @Command(separator = " ") annotation also affects how picocli parses the command line. See also Custom Separators.

14.17. Hidden Options and Parameters

Options and Parameters with the hidden attribute set to true will not be shown in the usage help message. This is useful for example when a parameter at some index is captured into multiple fields: by default each of these fields would be shown in the usage message, which would be confusing for users.

For example, the all field below is annotated as hidden = true:

Java
@Command()
class App {
    @Parameters(index = "0",    description = "destination host")  InetAddress host;
    @Parameters(index = "1",    description = "destination port")  int port;
    @Parameters(index = "2..*", description = "files to transfer") String[] files;

    @Parameters(hidden = true) String[] all;
}
Kotlin
@Command
class App {
    @Parameters(index = "0",    description = ["destination host"])
    lateinit var host: InetAddress
    @Parameters(index = "1",    description = ["destination port"])
    var port = 0
    @Parameters(index = "2..*", description = ["files to transfer"])
    lateinit var files: Array<String>

    @Parameters(hidden = true)
    lateinit var all: Array<String>
}

The above will generate the following usage help message, where the all field is not shown:

Usage: <main class> <host> <port> [<files>...]
      host    destination host
      port    destination port
      files   files to transfer

14.18. Show At Files

As of picocli 4.2, an entry for @<filename> can be shown in the options and parameters list of the usage help message of a command with the @Command(showAtFileInUsageHelp = true) annotation.

14.18.1. Example

Example command:

Java
@Command(name = "myapp", showAtFileInUsageHelp = true,
         mixinStandardHelpOptions = true, description = "Example command.")
class MyApp {
    @Parameters(description = "A file.") File file;
}
Kotlin
@Command(name = "myapp", showAtFileInUsageHelp = true,
         mixinStandardHelpOptions = true, description = ["Example command."])
class MyApp {
    @Parameters(description = ["A file."])
    lateinit var file: File
}

The usage help message for this command looks like this:

Usage: myapp [-hV] [@<filename>...] <file>
Example command.
      [@<filename>...]   One or more argument files containing options.
      <file>             A file.
  -h, --help             Show this help message and exit.
  -V, --version          Print version information and exit.

14.18.2. Changing the At File Entry Location

By default, the @<filename> entry is shown before the positional parameters in the synopsis as well as in the parameters list.

This can be changed with the Help API for reordering sections. For example:

Java
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_AT_FILE_PARAMETER;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST_HEADING;

@Command(name = "myapp", showAtFileInUsageHelp = true,
        mixinStandardHelpOptions = true, description = "Example command.")
class MyApp {
    @Parameters(description = "A file.") File file;

    public static void main(String... args) {
        CommandLine cmd = new CommandLine(new MyApp());
        List<String> copy = new ArrayList<>(cmd.getHelpSectionKeys());
        copy.remove(SECTION_KEY_AT_FILE_PARAMETER);
        copy.add(copy.indexOf(SECTION_KEY_COMMAND_LIST_HEADING), SECTION_KEY_AT_FILE_PARAMETER);
        cmd.setHelpSectionKeys(copy);

        cmd.usage(System.out);
    }
}
Kotlin
import picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST_HEADING
import picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_AT_FILE_PARAMETER
// ...

@Command(name = "myapp", showAtFileInUsageHelp = true,
         mixinStandardHelpOptions = true, description = ["Example command."])
class MyApp {
    @Parameters(description = ["A file."]) lateinit var file: File

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val cmd = CommandLine(MyApp())
            val copy: MutableList<String> = ArrayList(cmd.helpSectionKeys)
            copy.remove(SECTION_KEY_AT_FILE_PARAMETER)
            copy.add(copy.indexOf(SECTION_KEY_COMMAND_LIST_HEADING), SECTION_KEY_AT_FILE_PARAMETER)
            cmd.helpSectionKeys = copy
            cmd.usage(System.out)
        }
    }
}

The resulting usage help message shows the @<filename> entry has moved to the bottom, following the options list:

Usage: myapp [-hV] [@<filename>...] <file>
Example command.
      <file>             A file.
  -h, --help             Show this help message and exit.
  -V, --version          Print version information and exit.
      [@<filename>...]   One or more argument files containing options.

14.18.3. Changing the At File Entry Text

Both the label and the description of the @<filename> entry have been defined with custom variables, to allow applications to change the text. The variables are:

  • picocli.atfile.label

  • picocli.atfile.description

By setting the above variables in either system properties, environment variables or the resource bundle for a command, the text can be customized.

For example, if we define these system properties:

System.setProperty("picocli.atfile.label", "my@@@@file");
System.setProperty("picocli.atfile.description", "@files rock!");

then the usage help for our above example changes to this:

Usage: myapp [-hV] [my@@@@file...] <file>
Example command.
      [my@@@@file...]   @files rock!
      <file>            A file.
  -h, --help            Show this help message and exit.
  -V, --version         Print version information and exit.

The description of the @<filename> entry can also be specified in a resource bundle for internationalization and localization. The descriptionKey for the @<filename> entry in resource bundles is:

  • picocli.atfile

14.19. Show End of Options

As of picocli 4.3, an entry for the -- End of Options delimiter can be shown in the options list of the usage help message of a command with the @Command(showEndOfOptionsDelimiterInUsageHelp = true) annotation.

14.19.1. Example

Example command:

Java
@Command(name = "myapp", showEndOfOptionsDelimiterInUsageHelp = true,
         mixinStandardHelpOptions = true, description = "Example command.")
class MyApp {
    @Parameters(description = "A file.") File file;
}
Kotlin
@Command(name = "myapp", showEndOfOptionsDelimiterInUsageHelp = true,
         mixinStandardHelpOptions = true, description = ["Example command."])
class MyApp {
    @Parameters(description = ["A file."])
    lateinit var file: File
}

The usage help message for this command looks like this:

Usage: myapp [-hV] [--] <file>
Example command.
      <file>      A file.
  -h, --help      Show this help message and exit.
  -V, --version   Print version information and exit.
  --              This option can be used to separate command-line options from
                    the list of positional parameters.

14.19.2. Changing the End of Options Entry Location

By default, the -- End of Options entry is shown between the options and the positional parameters in the synopsis, and at the end of the options list.

This can be changed with the Help API for reordering sections. For example:

Java
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_END_OF_OPTIONS;
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST;

@Command(name = "myapp", showEndOfOptionsDelimiterInUsageHelp = true,
        mixinStandardHelpOptions = true, description = "Example command.")
class MyApp {
    @Parameters(description = "A file.") File file;

    public static void main(String... args) {
        CommandLine cmd = new CommandLine(new MyApp());
        List<String> copy = new ArrayList<>(cmd.getHelpSectionKeys());
        copy.remove(SECTION_KEY_END_OF_OPTIONS);
        copy.add(copy.indexOf(SECTION_KEY_OPTION_LIST), SECTION_KEY_END_OF_OPTIONS);
        cmd.setHelpSectionKeys(copy);

        cmd.usage(System.out);
    }
}
Kotlin
import picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_OPTION_LIST
import picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_END_OF_OPTIONS
// ...

@Command(name = "myapp", showEndOfOptionsDelimiterInUsageHelp = true,
         mixinStandardHelpOptions = true, description = ["Example command."])
class MyApp {
    @Parameters(description = ["A file."])
    lateinit var file: File

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val cmd = CommandLine(MyApp())
            val copy: MutableList<String> = ArrayList(cmd.helpSectionKeys)
            copy.remove(SECTION_KEY_END_OF_OPTIONS)
            copy.add(copy.indexOf(SECTION_KEY_OPTION_LIST), SECTION_KEY_END_OF_OPTIONS)
            cmd.helpSectionKeys = copy

            cmd.usage(System.out)
        }
    }
}

The resulting usage help message shows the -- End of Options entry has moved up, to the beginning the options list:

Usage: myapp [-hV] [--] <file>
Example command.
      <file>      A file.
  --              This option can be used to separate command-line options from
                    the list of positional parameters.
  -h, --help      Show this help message and exit.
  -V, --version   Print version information and exit.

14.19.3. Changing the End of Options Entry Text

The description of the -- End of Options delimiter entry has been defined with a custom variable, to allow applications to change the text. The variable is:

  • picocli.endofoptions.description

By setting the above variable in either system properties, environment variables or the resource bundle for a command, the text can be customized.

For example, if we define this system property:

System.setProperty("picocli.endofoptions.description", "End of options. Remainder are positional parameters.");

then the usage help for our above example changes to this:

Usage: myapp [-hV] [--] <file>
Example command.
      <file>      A file.
  -h, --help      Show this help message and exit.
  -V, --version   Print version information and exit.
  --              End of options. Remainder are positional parameters.

The description of the End of Options delimiter entry can also be specified in a resource bundle for internationalization and localization. The descriptionKey for the End of Options delimiter entry in resource bundles is:

  • picocli.endofoptions

14.20. Show Default Values

14.20.1. ${DEFAULT-VALUE} Variable

From picocli 3.2, it is possible to embed the default values in the description for an option or positional parameter by specifying the variable ${DEFAULT-VALUE} in the description text. Picocli uses reflection to get the default values from the annotated fields.

The variable is replaced with the default value regardless of the @Command(showDefaultValues) attribute and regardless of the @Option(showDefaultValues) or @Parameters(showDefaultValues) attribute.

Java
class DefaultValues {
    @Option(names = {"-f", "--file"}, defaultValue = "config.xml",
            description = "the file to use (default: ${DEFAULT-VALUE})")
    File file;
}

CommandLine.usage(new DefaultValues(), System.out);
Kotlin
class DefaultValues {
    @Option(names = ["-f", "--file"], defaultValue = "config.xml",
        description = ["the file to use (default: \${DEFAULT-VALUE})"])
    lateinit var file: File
}

fun main(args: Array<String>) = CommandLine(DefaultValues()).usage(System.out)

This produces the following usage help:

Usage: <main class> [-f=<file>]
  -f, --file=<file>   the file to use (default: config.xml)

14.20.2. ${COMPLETION-CANDIDATES} Variable

Similarly, it is possible to embed the completion candidates in the description for an option or positional parameter by specifying the variable ${COMPLETION-CANDIDATES} in the description text.

This works for java enum classes and for options or positional parameters of non-enum types for which completion candidates are specified.

Java
enum Lang { java, groovy, kotlin, javascript, frege, clojure }

static class MyAbcCandidates extends ArrayList<String> {
    MyAbcCandidates() { super(Arrays.asList("A", "B", "C")); }
}

class ValidValuesDemo {
    @Option(names = "-l", description = "Enum values: ${COMPLETION-CANDIDATES}")
    Lang lang = null;

    @Option(names = "-o", completionCandidates = MyAbcCandidates.class,
            description = "Candidates: ${COMPLETION-CANDIDATES}")
    String option;
}

CommandLine.usage(new ValidValuesDemo(), System.out);
Kotlin
enum class Lang { java, groovy, kotlin, javascript, frege, clojure }

class MyAbcCandidates : ArrayList<String?>(listOf("A", "B", "C"))

class ValidValuesDemo {
    @Option(names = ["-l"], description = ["Enum values: \${COMPLETION-CANDIDATES}"])
    lateinit var lang: Lang

    @Option(names = ["-o"], completionCandidates = MyAbcCandidates::class,
            description = ["Candidates: \${COMPLETION-CANDIDATES}"])
    lateinit var option: String
}

fun main(args: Array<String>) = CommandLine(ValidValuesDemo()).usage(System.out)

This produces the following usage help:

Usage: <main class> [-l=<lang>] [-o=<option>]
  -l=<lang>     Enum values: java, groovy, kotlin, javascript, frege, clojure
  -o=<option>   Candidates: A, B, C

14.20.3. Legacy Configuration for Displaying Default Values

Prior to picocli 3.2, you need to use the @Command(showDefaultValues = true) attribute to append the default value of all non-null options and positional parameters to the description column.

Additionally, picocli 3.0 introduced a showDefaultValue attribute to the @Option and @Parameters annotation. This allows you to specify for each individual option and positional parameter whether its default value should be shown in the usage help. This attribute accepts three values:

  • ALWAYS - always display the default value of this option or positional parameter, even null values, regardless what value of showDefaultValues was specified on the command

  • NEVER - don’t show the default value for this option or positional parameter, regardless what value of showDefaultValues was specified on the command

  • ON_DEMAND - (this is the default) only show the default value for this option or positional parameter if showDefaultValues was specified on the command

These legacy mechanisms still work but for maximum flexibility use the variables explained above.

15. ANSI Colors and Styles

Using colors in your command’s output does not just look good: by contrasting important elements like option names from the rest of the message, it reduces the cognitive load on the user.

15.1. Colorized Example

Below shows the same usage help message as shown in Expanded Example, with ANSI escape codes enabled.

Screenshot of usage help with Ansi codes enabled

15.2. Usage Help with Styles and Colors

You can use colors and styles in the descriptions, header and footer of the usage help message.

Picocli supports a custom markup notation for mixing colors and styles in text, following a convention introduced by Jansi, where @| starts a styled section, and |@ ends it. Immediately following the @| is a comma-separated list of colors and styles, so @|STYLE1[,STYLE2]…​ text|@. For example:

Java
@Command(description = "Custom @|bold,underline styles|@ and @|fg(red) colors|@.")
Kotlin
@Command(description = ["Custom @|bold,underline styles|@ and @|fg(red) colors|@."])

Description with Ansi styles and colors

Table 5. Pre-defined styles and colors that can be used in descriptions and headers using the @|STYLE1[,STYLE2]…​ text|@ notation
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
@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 { /* ... */ }
Kotlin
@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
import picocli.CommandLine.Help.Ansi;
// ...
String str = Ansi.AUTO.string("@|bold,green,underline Hello, colored world!|@");
System.out.println(str);
Kotlin
import picocli.CommandLine.Help.Ansi;
// ...
val str: String = Ansi.AUTO.string("@|bold,green,underline Hello, colored world!|@")
println(str)

15.4. More Colors

Most terminals support a 256 color indexed palette:

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|@.

256 color indexed palette

15.5. Configuring Fixed Elements

15.5.1. Color Scheme

Picocli uses a default color scheme for options, parameters and commands. There are no annotations to modify this color scheme, but it can be changed programmatically.

The below code snippet shows how a custom color scheme can be specified to configure the usage help message style:

Java
// see also CommandLine.Help.defaultColorScheme()
ColorScheme colorScheme = new ColorScheme.Builder()
        .commands    (Style.bold, Style.underline)    // combine multiple styles
        .options     (Style.fg_yellow)                // yellow foreground color
        .parameters  (Style.fg_yellow)
        .optionParams(Style.italic)
        .errors      (Style.fg_red, Style.bold)
        .stackTraces (Style.italic)
        .applySystemProperties() // optional: allow end users to customize
        .build();

CommandLine.usage(annotatedObject, System.out, colorScheme);
// ...
Kotlin
// see also CommandLine.Help.defaultColorScheme()
val colorScheme: Help.ColorScheme = ColorScheme.Builder()
        .commands    (Style.bold, Style.underline)    // combine multiple styles
        .options     (Style.fg_yellow)                // yellow foreground color
        .parameters  (Style.fg_yellow)
        .optionParams(Style.italic)
        .errors      (Style.fg_red, Style.bold)
        .stackTraces (Style.italic)
        .applySystemProperties() // optional: allow end users to customize
        .build()

CommandLine.usage(annotatedObject, System.out, colorScheme)

When using the execute API, you can configure a ColorScheme like this:

Java
public static void main(String[] args) {
    ColorScheme colorScheme = createColorScheme();

    new CommandLine(new MyApp())
            .setColorScheme(colorScheme) // use this color scheme in the usage help message
            .execute(args);
}
Kotlin
fun main(args: Array<String>) {
    val colorScheme: ColorScheme = createColorScheme()

    CommandLine(MyApp())
            .setColorScheme(colorScheme) // use this color scheme in the usage help message
            .execute(*args)
}

15.5.2. Color Scheme Overrides

The following system properties override the color scheme styles. This allows end users to adjust for their individual terminal color setup.

System Properties to Override the Color Scheme
picocli.color.commands
picocli.color.options
picocli.color.parameters
picocli.color.optionParams
picocli.color.errors
picocli.color.stackTraces

For example:

java -Dpicocli.color.options=blink,blue -Dpicocli.color.parameters=reverse com.app.Main

System property values may specify multiple comma separated styles.

15.6. Supported Platforms

Picocli will only emit ANSI escape codes on supported platforms. For details, see Heuristics for Enabling ANSI.

15.6.1. Unix and Linux

Most Unix and Linux platforms support ANSI colors natively. On Windows, when picocli detects it is running under a Unix variant like Cygwin or MSYS(2) on Windows it will display ANSI colors and styles, otherwise it will not emit ANSI codes.

15.6.2. Windows

Displaying colors on Windows Command Console and PowerShell requires a bit of extra work.

The easiest way to accomplish this is to use the Jansi library in your application.

None of the below is mandatory. If not supported, picocli will simply not emit ANSI escape codes, and everything will work without colors.
Jansi

To use Jansi, you need to enable it in your application. For example:

Java
import org.fusesource.jansi.AnsiConsole;
// ...
public static void main(String[] args) {
    AnsiConsole.systemInstall(); // enable colors on Windows
    new CommandLine(new WindowsJansiDemo()).execute(args);
    AnsiConsole.systemUninstall(); // cleanup when done
}
Kotlin
import org.fusesource.jansi.AnsiConsole;
// ...
fun main(args: Array<String>) {
    AnsiConsole.systemInstall() // enable colors on Windows
    CommandLine(WindowsJansiDemo()).execute(*args)
    AnsiConsole.systemUninstall() // cleanup when done
}
Jansi in GraalVM Native Images

In Java applications compiled to a GraalVM native image for Windows, Jansi by itself is insufficient to show colors. This is partly because GraalVM requires configuration and partly because Jansi internally depends on non-standard system properties, without a graceful fallback if these properties are absent (as is the case in GraalVM).

Users may be interested in combining Jansi with picocli-jansi-graalvm until this issue is fixed. Example usage:

Java
import picocli.jansi.graalvm.AnsiConsole; // not org.fusesource.jansi.AnsiConsole
// ...
public static void main(String[] args) {
    int exitCode;
    try (AnsiConsole ansi = AnsiConsole.windowsInstall()) {
        exitCode = new CommandLine(new MyApp()).execute(args);
    }
    System.exit(exitCode);
}
Kotlin
import picocli.jansi.graalvm.AnsiConsole // not org.fusesource.jansi.AnsiConsole
// ...
fun main(args: Array<String>) {
    val exitCode: Int
    AnsiConsole.windowsInstall().use
        { exitCode = CommandLine(MyApp()).execute(*args) }
    System.exit(exitCode)
}
For Reference: Without Jansi on Windows 10 Command Console and PowerShell
As of this writing, the practical way to get colors in Command Console and PowerShell is to use Jansi. The below is just for reference.

Starting from Windows 10, the Windows Console supports ANSI escape sequences, but it’s not enabled by default. Unless the specific software you’re using (e.g. java) enables ANSI processing by calling the SetConsoleMode API with the ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x0400) flag (java doesn’t), you won’t see colors or get ANSI processing for that application.

Note that there is a registry setting to change the global default from opt in to opt out. This Stack Overflow answer has more details.

Note that picocli’s heuristics for enabling ANSI currently do not include detecting whether support for Virtual Terminal / ANSI escape sequences has been turned on or off via SetConsoleMode or a registry change. So just making these changes is not sufficient to let a picocli-based application show colors. Applications that enabled colors via SetConsoleMode may want to set system property picocli.ansi to tty. Environments that enabled colors via a Windows Registry change may want to set environment variable CLICOLOR=1.
For Reference: Windows Subsystem for Linux (WSL)

You may want to recommend your users to try getting Windows Subsystem for Linux (WSL). This lets them run a GNU/Linux environment — including most command-line tools, utilities, and applications — directly on Windows, unmodified, without the overhead of a virtual machine. Picocli-based applications will show ANSI colors in WSL by default.

For Reference: 3rd Party Software

In Windows version prior to 10, the Windows command console doesn’t support output coloring by default. One option is for end users to install either Cmder, ConEmu, ANSICON or Mintty (used by default in GitBash and Cygwin) to add coloring support to their Windows command console.

15.7. Forcing ANSI On/Off

You can force picocli to either always use ANSI codes or never use ANSI codes regardless of the platform:

  • Setting system property picocli.ansi to true forces picocli to use ANSI codes; setting picocli.ansi to false forces picocli to not use ANSI codes. It may be useful for your users to mention this system property in the documentation for your command line application.

  • Setting system property picocli.ansi to tty (case-insensitive) forces picocli to use ANSI codes only if picocli guesses that the process is using an interactive console: either System.console() != null or picocli guesses the application is running in a pseudo-terminal pty on a Linux emulator in Windows. Otherwise the below heuristics are used to determine whether to output ANSI escape codes.

  • You can decide to force disable or force enable ANSI escape codes programmatically by specifying Ansi.ON or Ansi.OFF when invoking CommandLine.usage. This overrides the value of system property picocli.ansi. For example:

Java
import picocli.CommandLine.Help.Ansi;

// print usage help message to STDOUT without ANSI escape codes
CommandLine.usage(new App(), System.out, Ansi.OFF);
Kotlin
import picocli.CommandLine.Help.Ansi

// print usage help message to STDOUT without ANSI escape codes
CommandLine.usage(App(), System.out, Ansi.OFF)

15.8. Heuristics for Enabling ANSI

Below is the exact sequence of steps picocli uses to determine whether or not to emit ANSI escape codes.

  1. If Ansi.ON or Ansi.OFF is explicitly specified, either via system property picocli.ansi or programmatically, this value is used.

  2. ANSI is disabled when environment variable NO_COLOR is defined (regardless of its value).

  3. ANSI is enabled when environment variable CLICOLOR_FORCE is defined and has any value other than 0 (zero).

  4. ANSI is enabled when system property os.name starts with "Windows" and JAnsi Console is installed.

  5. ANSI is disabled when environment variable CLICOLOR == 0.

  6. ANSI is disabled when environment variable ConEmuANSI == OFF.

  7. ANSI is disabled when Picocli guesses the program’s output stream is not connected to a terminal: when System.console() returns null. This check is omitted if picocli guesses the program is running in a Windows Cygwin, MSYS or MSYS2 environment: when system property os.name starts with "Windows" and either environment variable TERM contains cygwin or starts with xterm or environment variable OSTYPE is defined.

  8. ANSI is enabled when environment variable ANSICON is defined.

  9. ANSI is enabled when environment variable CLICOLOR == 1.

  10. ANSI is enabled when environment variable ConEmuANSI == ON.

  11. ANSI is enabled when picocli detects the program is running in a non-Windows OS (system property os.name does not start with "Windows").

  12. ANSI is enabled when picocli guesses the program is running in a Cygwin, MSYS or MSYS2 environment (either environment variable TERM contains cygwin or starts with xterm or environment variable OSTYPE is defined).

ANSI escape codes are not emitted if none of the above apply.

16. Usage Help API

For further customization of the usage help message, picocli has a Help API. The Help class provides a number of high-level operations, and a set of components like Layout, TextTable, IOptionRenderer, etc., that can be used to build custom help messages. Details of the Help API are out of scope for this document, but the following sections give some idea of what is possible.

16.1. Reordering Sections

One thing you may want to do is reorder sections of the usage message or add custom sections.

Picocli 3.9 introduces new API to facilitate customizing the usage help message: IHelpFactory allows applications to plug in Help subclasses, and IHelpSectionRenderer allows applications to add custom sections to the usage help message, or redefine existing sections.

The usage help message is no longer hard-coded, but is now constructed from the section renderers defined in CommandLine::getHelpSectionMap (or UsageMessageSpec::sectionMap for a single CommandSpec). By default this map contains the predefined section renderers:

Java
import static picocli.CommandLine.Model.UsageMessageSpec.*;
// ...

// The default section renderers delegate to methods in Help for their implementation
// (using Java 8 lambda notation for brevity):
Map<String, IHelpSectionRenderer> map = new HashMap<>();
map.put(SECTION_KEY_HEADER_HEADING,         help -> help.headerHeading());
map.put(SECTION_KEY_HEADER,                 help -> help.header());

//e.g. Usage:
map.put(SECTION_KEY_SYNOPSIS_HEADING,       help -> help.synopsisHeading());

//e.g. <cmd> [OPTIONS] <subcmd> [COMMAND-OPTIONS] [ARGUMENTS]
map.put(SECTION_KEY_SYNOPSIS,               help -> help.synopsis(help.synopsisHeadingLength()));

//e.g. %nDescription:%n%n
map.put(SECTION_KEY_DESCRIPTION_HEADING,    help -> help.descriptionHeading());

//e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
map.put(SECTION_KEY_DESCRIPTION,            help -> help.description());

//e.g. %nPositional parameters:%n%n
map.put(SECTION_KEY_PARAMETER_LIST_HEADING, help -> help.parameterListHeading());

//e.g. [FILE...] the files to convert
map.put(SECTION_KEY_PARAMETER_LIST,         help -> help.parameterList());

//e.g. %nOptions:%n%n
map.put(SECTION_KEY_OPTION_LIST_HEADING,    help -> help.optionListHeading());

//e.g. -h, --help   displays this help and exits
map.put(SECTION_KEY_OPTION_LIST,            help -> help.optionList());

//e.g. %nCommands:%n%n
map.put(SECTION_KEY_COMMAND_LIST_HEADING,   help -> help.commandListHeading());

//e.g.    add       this command adds the frup to the frooble
map.put(SECTION_KEY_COMMAND_LIST,           help -> help.commandList());
map.put(SECTION_KEY_EXIT_CODE_LIST_HEADING, help -> help.exitCodeListHeading());
map.put(SECTION_KEY_EXIT_CODE_LIST,         help -> help.exitCodeList());
map.put(SECTION_KEY_FOOTER_HEADING,         help -> help.footerHeading());
map.put(SECTION_KEY_FOOTER,                 help -> help.footer());
Kotlin
import picocli.CommandLine.Model.UsageMessageSpec.*
// ...

// The default section renderers delegate to methods in Help for their implementation
// (using lambda expressions for brevity):
val map: MutableMap<String, IHelpSectionRenderer> = HashMap()
map[SECTION_KEY_HEADER_HEADING] =
    IHelpSectionRenderer { help: Help -> help.headerHeading() }
map[SECTION_KEY_HEADER] =
    IHelpSectionRenderer { help: Help -> help.header() }

//e.g. Usage:
map[SECTION_KEY_SYNOPSIS_HEADING] =
    IHelpSectionRenderer { help: Help -> help.synopsisHeading() }

//e.g. <cmd> [OPTIONS] <subcmd> [COMMAND-OPTIONS] [ARGUMENTS]
map[SECTION_KEY_SYNOPSIS] =
    IHelpSectionRenderer { help: Help -> help.synopsis(help.synopsisHeadingLength()) }

//e.g. %nDescription:%n%n
map[SECTION_KEY_DESCRIPTION_HEADING] =
    IHelpSectionRenderer { help: Help -> help.descriptionHeading() }

//e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
map[SECTION_KEY_DESCRIPTION] =
    IHelpSectionRenderer  { help: Help -> help.description() }

//e.g. %nPositional parameters:%n%n
map[SECTION_KEY_PARAMETER_LIST_HEADING] =
    IHelpSectionRenderer { help: Help -> help.parameterListHeading() }

//e.g. [FILE...] the files to convert
map[SECTION_KEY_PARAMETER_LIST] =
    IHelpSectionRenderer { help: Help -> help.parameterList() }

//e.g. %nOptions:%n%n
map[SECTION_KEY_OPTION_LIST_HEADING] =
    IHelpSectionRenderer { help: Help -> help.optionListHeading() }

//e.g. -h, --help   displays this help and exits
map[SECTION_KEY_OPTION_LIST] =
    IHelpSectionRenderer { help: Help -> help.optionList() }

//e.g. %nCommands:%n%n
map[SECTION_KEY_COMMAND_LIST_HEADING] =
    IHelpSectionRenderer { help: Help -> help.commandListHeading() }

//e.g.    add       this command adds the frup to the frooble
map[SECTION_KEY_COMMAND_LIST] =
    IHelpSectionRenderer { help: Help -> help.commandList() }
map[SECTION_KEY_EXIT_CODE_LIST_HEADING] =
    IHelpSectionRenderer { help: Help -> help.exitCodeListHeading() }
map[SECTION_KEY_EXIT_CODE_LIST] =
    IHelpSectionRenderer { help: Help -> help.exitCodeList() }
map[SECTION_KEY_FOOTER_HEADING] =
    IHelpSectionRenderer { help: Help -> help.footerHeading() }
map[SECTION_KEY_FOOTER] =
    IHelpSectionRenderer { help: Help -> help.footer() }

Applications can add, remove or replace sections in this map. The CommandLine::getHelpSectionKeys method (or UsageMessageSpec::sectionKeys for a single CommandSpec) returns the section keys in the order that the usage help message should render the sections. The default keys are (in order):

  1. SECTION_KEY_HEADER_HEADING

  2. SECTION_KEY_HEADER

  3. SECTION_KEY_SYNOPSIS_HEADING

  4. SECTION_KEY_SYNOPSIS

  5. SECTION_KEY_DESCRIPTION_HEADING

  6. SECTION_KEY_DESCRIPTION

  7. SECTION_KEY_PARAMETER_LIST_HEADING

  8. SECTION_KEY_AT_FILE_PARAMETER

  9. SECTION_KEY_PARAMETER_LIST

  10. SECTION_KEY_OPTION_LIST_HEADING

  11. SECTION_KEY_OPTION_LIST

  12. SECTION_KEY_COMMAND_LIST_HEADING

  13. SECTION_KEY_COMMAND_LIST

  14. SECTION_KEY_EXIT_CODE_LIST_HEADING

  15. SECTION_KEY_EXIT_CODE_LIST

  16. SECTION_KEY_FOOTER_HEADING

  17. SECTION_KEY_FOOTER

This ordering may be modified with the CommandLine::setHelpSectionKeys setter method (or UsageMessageSpec::sectionKeys(List) for a single CommandSpec).

16.1.1. Custom Help Section Example

The below example shows how to add a custom Environment Variables section to the usage help message.

Java
// help section keys
final String SECTION_KEY_ENV_HEADING = "environmentVariablesHeading";
final String SECTION_KEY_ENV_DETAILS = "environmentVariables";
// ...

// the data to display
Map<String, String> env = new LinkedHashMap<>();
env.put("FOO", "explanation of foo");
env.put("BAR", "explanation of bar");
env.put("XYZ", "xxxx yyyy zzz");

// register the custom section renderers
CommandLine cmd = new CommandLine(new MyApp());
cmd.getHelpSectionMap().put(SECTION_KEY_ENV_HEADING,
                            help -> help.createHeading("Environment Variables:%n"));
cmd.getHelpSectionMap().put(SECTION_KEY_ENV_DETAILS,
                            help -> help.createTextTable(env).toString());

// specify the location of the new sections
List<String> keys = new ArrayList<>(cmd.getHelpSectionKeys());
int index = keys.indexOf(CommandLine.Model.UsageMessageSpec.SECTION_KEY_FOOTER_HEADING);
keys.add(index, SECTION_KEY_ENV_HEADING);
keys.add(index + 1, SECTION_KEY_ENV_DETAILS);
cmd.setHelpSectionKeys(keys);
Kotlin
// help section keys
val SECTION_KEY_ENV_HEADING = "environmentVariablesHeading"
val SECTION_KEY_ENV_DETAILS = "environmentVariables"
// ...

// the data to display
val env: MutableMap<String, String> = LinkedHashMap()
env["FOO"] = "explanation of foo"
env["BAR"] = "explanation of bar"
env["XYZ"] = "xxxx yyyy zzz"

// register the custom section renderers
val cmd = CommandLine(MyApp())
cmd.helpSectionMap[SECTION_KEY_ENV_HEADING] =
    IHelpSectionRenderer { help: Help -> help.createHeading("Environment Variables:%n") }
cmd.helpSectionMap[SECTION_KEY_ENV_DETAILS] =
    IHelpSectionRenderer { help: Help -> help.createTextTable(env).toString() }

// specify the location of the new sections
val keys = ArrayList(cmd.helpSectionKeys)
val index = keys.indexOf(SECTION_KEY_FOOTER_HEADING)
keys.add(index, SECTION_KEY_ENV_HEADING)
keys.add(index + 1, SECTION_KEY_ENV_DETAILS)
cmd.helpSectionKeys = keys

More examples for customizing the usage help message are here.

16.2. Custom Layout

Picocli also supports unconventional option list layouts. An example of an unconventional layout is the zip application, which shows multiple options per row:

Java
CommandLine.usage(new ZipHelpDemo(), System.out);
Kotlin
CommandLine.usage(ZipHelpDemo(), System.out)
Copyright (c) 1990-2008 Info-ZIP - Type 'zip "-L"' for software license.
Zip 3.0 (July 5th 2008). Command:
zip [-options] [-b path] [-t mmddyyyy] [-n suffixes] [zipfile list] [-xi list]
  The default action is to add or replace zipfile entries from list, which
  can include the special name - to compress standard input.
  If zipfile and list are omitted, zip compresses stdin to stdout.
  -f   freshen: only changed files  -u   update: only changed or new files
  -d   delete entries in zipfile    -m   move into zipfile (delete OS files)
  -r   recurse into directories     -j   junk (don't record) directory names
  -0   store only                   -l   convert LF to CR LF (-ll CR LF to LF)
  -1   compress faster              -9   compress better
  -q   quiet operation              -v   verbose operation/print version info
  -c   add one-line comments        -z   add zipfile comment
  -@   read names from stdin        -o   make zipfile as old as latest entry
  -x   exclude the following names  -i   include only the following names
  -F   fix zipfile (-FF try harder) -D   do not add directory entries
  -A   adjust self-extracting exe   -J   junk zipfile prefix (unzipsfx)
  -T   test zipfile integrity       -X   eXclude eXtra file attributes
  -y   store symbolic links as the link instead of the referenced file
  -e   encrypt                      -n   don't compress these suffixes
  -h2  show more help

This can be achieved in picocli by subclassing the Help.Layout class. See the CustomLayoutDemo class in the picocli tests for how to achieve this.

17. Subcommands

Sophisticated command-line tools, like the prominent git tool, have many subcommands (e.g., commit, push, …), each with its own set of options and positional parameters. Picocli makes it very easy to have commands with subcommands, and sub-subcommands, to any level of depth.

17.1. Subcommand Examples

If you want to jump ahead and see some examples first, these resources may be helpful:

  • The Executing Subcommands section below has an example that uses both the @Command(subcommands = <class>) syntax and the @Command-annotated method syntax.

  • The Quick Guide has an example application featuring subcommands, with a detailed explanation.

  • The picocli-examples code module has a subcommand section with several minimal code samples, explaining the use of subcommands both via methods and when defined in their own class.

  • The picocli-examples code module also shows subcommand examples in other languages, like Scala and Kotlin, as well as Java.

17.2. Registering Subcommands Declaratively

Subcommands can be registered declaratively with the @Command annotation’s subcommands attribute since picocli 0.9.8. This is the recommended way when you want to use the picocli annotation processor to generate documentation, autocompletion scripts or GraalVM configuration files.

Java
@Command(subcommands = {
    GitStatus.class,
    GitCommit.class,
    GitAdd.class,
    GitBranch.class,
    GitCheckout.class,
    GitClone.class,
    GitDiff.class,
    GitMerge.class,
    GitPush.class,
    GitRebase.class,
    GitTag.class
})
public class Git { /* ... */ }
Groovy
@Command(subcommands = [
    GitStatus.class,
    GitCommit.class,
    GitAdd.class,
    GitBranch.class,
    GitCheckout.class,
    GitClone.class,
    GitDiff.class,
    GitMerge.class,
    GitPush.class,
    GitRebase.class,
    GitTag.class
])
public class Git { /* ... */ }
Kotlin
@Command(subcommands = [
    GitStatus::class,
    GitCommit::class,
    GitAdd::class,
    GitBranch::class,
    GitCheckout::class,
    GitClone::class,
    GitDiff::class,
    GitMerge::class,
    GitPush::class,
    GitRebase::class,
    GitTag::class]
)
public class Git { /* ... */ }
Scala
@Command(subcommands = Array(
    classOf[GitStatus],
    classOf[GitCommit],
    classOf[GitAdd],
    classOf[GitBranch],
    classOf[GitCheckout],
    classOf[GitClone],
    classOf[GitDiff],
    classOf[GitMerge],
    classOf[GitPush],
    classOf[GitRebase],
    classOf[GitTag]
))
class Git { /* ... */ }

Subcommands referenced in a subcommands attribute must have a @Command annotation with a name attribute, or an exception is thrown from the CommandLine constructor. This name will be used both for generating usage help and for recognizing subcommands when parsing the command line. Command names are case-sensitive by default, but this is customizable.

Custom type converters registered on a CommandLine instance will apply to all subcommands that were declared on the main command with the subcommands annotation.

Subcommands referenced in a subcommands attribute need to have a public no-argument constructor to be instantiated, unless a Custom Factory is installed to instantiate classes.

Prior to picocli 4.2, the declared subcommands were instantiated immediately when the top-level CommandLine (the new CommandLine(new Git()) object in the above example) was constructed.

From picocli 4.2, the declared subcommands are not instantiated until they are matched on the command line, unless they have a @Spec or @ParentCommand-annotated field; these are injected during initialization, and in order to inject them the subcommand is instantiated during initialization.

17.3. Subcommands as Methods

As of picocli 3.6 it is possible to register subcommands in a very compact manner by having a @Command class with @Command-annotated methods. The methods are automatically registered as subcommands of the enclosing @Command class. See the command methods section for more details and examples.

The picocli-examples module has an minimal example application, demonstrating the definition of subcommands via methods. This example was coded in Java, Kotlin and Scala.

17.4. Registering Subcommands Programmatically

Subcommands can be registered with the CommandLine.addSubcommand method. You pass in the name of the command and the annotated object to populate with the subcommand options. The specified name is used by the parser to recognize subcommands in the command line arguments.

Java
CommandLine commandLine = new CommandLine(new Git())
        .addSubcommand("status",   new GitStatus())
        .addSubcommand("commit",   new GitCommit())
        .addSubcommand("add",      new GitAdd())
        .addSubcommand("branch",   new GitBranch())
        .addSubcommand("checkout", new GitCheckout())
        .addSubcommand("clone",    new GitClone())
        .addSubcommand("diff",     new GitDiff())
        .addSubcommand("merge",    new GitMerge())
        .addSubcommand("push",     new GitPush())
        .addSubcommand("rebase",   new GitRebase())
        .addSubcommand("tag",      new GitTag());
Groovy
def commandLine = new CommandLine(new Git())
        .addSubcommand("status",   new GitStatus())
        .addSubcommand("commit",   new GitCommit())
        .addSubcommand("add",      new GitAdd())
        .addSubcommand("branch",   new GitBranch())
        .addSubcommand("checkout", new GitCheckout())
        .addSubcommand("clone",    new GitClone())
        .addSubcommand("diff",     new GitDiff())
        .addSubcommand("merge",    new GitMerge())
        .addSubcommand("push",     new GitPush())
        .addSubcommand("rebase",   new GitRebase())
        .addSubcommand("tag",      new GitTag());
Kotlin
val commandLine = CommandLine(Git())
        .addSubcommand("status",   GitStatus())
        .addSubcommand("commit",   GitCommit())
        .addSubcommand("add",      GitAdd())
        .addSubcommand("branch",   GitBranch())
        .addSubcommand("checkout", GitCheckout())
        .addSubcommand("clone",    GitClone())
        .addSubcommand("diff",     GitDiff())
        .addSubcommand("merge",    GitMerge())
        .addSubcommand("push",     GitPush())
        .addSubcommand("rebase",   GitRebase())
        .addSubcommand("tag",      GitTag())
Scala
val commandLine: CommandLine  = new CommandLine(new Git())
        .addSubcommand("status",   new GitStatus())
        .addSubcommand("commit",   new GitCommit())
        .addSubcommand("add",      new GitAdd())
        .addSubcommand("branch",   new GitBranch())
        .addSubcommand("checkout", new GitCheckout())
        .addSubcommand("clone",    new GitClone())
        .addSubcommand("diff",     new GitDiff())
        .addSubcommand("merge",    new GitMerge())
        .addSubcommand("push",     new GitPush())
        .addSubcommand("rebase",   new GitRebase())
        .addSubcommand("tag",      new GitTag())

It is strongly recommended that subcommands have a @Command annotation with name and description attributes.

From picocli 3.1, the usage help synopsis of the subcommand shows not only the subcommand name but also the parent command name. For example, if the git command has a commit subcommand, the usage help for the commit subcommand shows Usage: git commit <options>.

Note on custom type converters: custom type converters are registered only with the subcommands and nested sub-subcommands that were added before the custom type was registered. To ensure a custom type converter is available to all subcommands, register the type converter last, after adding subcommands.

17.5. Executing Subcommands

The easiest way to design an application with subcommands is to let each command and subcommand either be a class that implements Runnable or Callable, or a @Command-annotated method.

This will allow you to parse the command line, deal with requests for help and requests for version information, deal with invalid user input, and invoke the business logic of the user-specified subcommand - all of that - in one line of code: the execute method.

For example:

Java
@Command(name = "foo", subcommands = Bar.class)
class Foo implements Callable<Integer> {
    @Option(names = "-x") int x;

    @Override public Integer call() {
        System.out.printf("hi from foo, x=%d%n", x);
        boolean ok = true;
        return ok ? 0 : 1; // exit code
    }

    public static void main(String... args) {
        int exitCode = new CommandLine(new Foo()).execute(args);
        System.exit(exitCode);
    }
}

@Command(name = "bar", description = "I'm a subcommand of `foo`")
class Bar implements Callable<Integer> {
    @Option(names = "-y") int y;

    @Override public Integer call() {
        System.out.printf("hi from bar, y=%d%n", y);
        return 23;
    }

    @Command(name = "baz", description = "I'm a subcommand of `bar`")
    int baz(@Option(names = "-z") int z) {
        System.out.printf("hi from baz, z=%d%n", z);
        return 45;
    }
}
Groovy
@Command(name = "foo", subcommands = Bar.class)
class Foo implements Callable<Integer> {
    @Option(names = "-x") def x

    @Override public Integer call() {
        println "hi from foo, x=$x"
        def ok = true;
        return ok ? 0 : 1 // exit code
    }

    public static void main(String... args) {
        def exitCode = new CommandLine(new Foo()).execute(args)
        System.exit(exitCode)
    }
}

@Command(name = "bar", description = "I'm a subcommand of `foo`")
class Bar implements Callable<Integer> {
    @Option(names = "-y") int y

    @Override public Integer call() {
        println "hi from bar, y=$y"
        return 23
    }

    @Command(name = "baz", description = "I'm a subcommand of `bar`")
    int baz(@Option(names = "-z") int z) {
        println "hi from baz, z=$z"
        return 45
    }
}
Kotlin
@Command(name = "foo", subcommands = [Bar::class])
class Foo : Callable<Int> {
    @Option(names = ["-x"]) var x: Int = 0

    override fun call(): Int {
        println("hi from foo, x=$x")
        var ok: Boolean = true
        return if (ok) 0 else 1 // exit code
    }
}

fun main(args: Array<String>) : Unit = exitProcess(CommandLine(Foo()).execute(*args))

@Command(name = "bar", description = ["I'm a subcommand of `foo`"])
class Bar : Callable<Int> {
    @Option(names = ["-y"]) var y: Int = 0

    override fun call(): Int {
        println("hi form bar, y=$y")
        return 23
    }

    @Command(name = "baz", description = ["I'm a subcommand of `bar`"])
    fun baz( @Option(names = ["-z"]) z: Int) : Int {
        println("hi from baz, z=$z")
        return 45
    }
}
Scala
@Command(name = "foo", subcommands = Array(classOf[Bar]))
class Foo extends Callable[Integer] {
    @Option(names = Array("-x"))
    var x = 0

    override def call: Integer = {
        println(s"hi from foo, $x")
        val ok = true
        if (ok) 0 else 1 // exit code
    }
}

object Foo{
    def main(args: Array[String]): Unit = {
        val exitCode : Int = new CommandLine(new Foo()).execute(args: _*)
        System.exit(exitCode)
    }
}

@Command(name = "bar", description = Array("I'm a subcommand of `foo`")) class Bar extends Callable[Integer] {
    @Option(names = Array("-y"))
    var y = 0

    override def call: Integer = {
        println(s"hi from bar, y=$y")
        23
    }

    @Command(name = "baz", description = Array("I'm a subcommand of `bar`"))
    def baz(@Option(names = Array("-z")) z: Int): Int = {
        println(s"hi from baz, z=$z")
        45
    }
}

To test our example on Linux, we created an alias foo that invokes our Java application. This could also be a script or a function that calls our Java program:

alias foo='java Foo'

Next, we call our top-level command with an option like this:

$ foo -x 123
hi from foo, x=123

#check the exit code
$ echo $?
0

We can also specify a subcommand:

$ foo -x 123 bar -y=456
hi from bar, y=456

#check the exit code
$ echo $?
23

And finally, we can also specify a sub-subcommand:

$ foo bar baz -z=789
hi from baz, z=789

#check the exit code
$ echo $?
45

As you can see, the last specified command or subcommand is executed and its exit code is returned. See also Executing Commands with Subcommands for details on configuring this.

17.6. Initialization Before Execution

Sometimes an application needs to take some action before execution. With a single command, you can simply do this in the beginning of the run or call method, but in an application with subcommands you don’t want to repeat this code in every subcommand.

One idea is to put the shared initialization logic in a custom execution strategy. For example:

Java
@Command(subcommands = {Sub1.class, Sub2.class, Sub3.class})
class MyApp implements Runnable {

    // A reference to this method can be used as a custom execution strategy
    // that first calls the init() method,
    // and then delegates to the default execution strategy.
    private int executionStrategy(ParseResult parseResult) {
        init(); // custom initialization to be done before executing any command or subcommand
        return new CommandLine.RunLast().execute(parseResult); // default execution strategy
    }

    private void init() {
        // ...
    }

    public static void main(String[] args) {
        MyApp app = new MyApp();
        new CommandLine(app)
                .setExecutionStrategy(app::executionStrategy)
                .execute(args);
    }

    // ...
}
Groovy
@Command(subcommands = [Sub1.class, Sub2.class, Sub3.class])
class MyApp implements Runnable {

    // A reference to this method can be used as a custom execution strategy
    // that first calls the init() method,
    // and then delegates to the default execution strategy.
    def int executionStrategy(ParseResult parseResult) {
        init(); // custom initialization to be done before executing any command or subcommand
        return new CommandLine.RunLast().execute(parseResult); // default execution strategy
    }

    def void init() {
        // ...
    }

    public static void main(String[] args) {
        def app = new MyApp();
        new CommandLine(app)
                .setExecutionStrategy(app.&executionStrategy)
                .execute(args);
    }

    // ...
}
Kotlin
@Command(subcommands = [Sub1::class, Sub2::class, Sub3::class])
class MyApp : Runnable {

    // A reference to this method can be used as a custom execution strategy
    // that first calls the init() method,
    // and then delegates to the default execution strategy.
    private fun executionStrategy(parseResult: ParseResult): Int {
        init() // custom initialization to be done before executing any command or subcommand
        return RunLast().execute(parseResult) // default execution strategy
    }

    private fun init() {
        // ...
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val app = MyApp()
            CommandLine(app)
                .setExecutionStrategy {
                     parseResult: ParseResult -> app.executionStrategy(parseResult) }
                .execute(*args)
        }
    }

    // ...
}
Scala
@Command(subcommands = Array(classOf[Sub1], classOf[Sub2], classOf[Sub3]))
class MyApp extends Runnable {

    // A reference to this method can be used as a custom execution strategy
    // that first calls the init() method,
    // and then delegates to the default execution strategy.
    private def executionStrategy(parseResult: ParseResult ) = {
        init() // custom initialization to be done before executing any command or subcommand
        new CommandLine.RunLast().execute(parseResult) // default execution strategy
    }

    private def init(): Unit = {
        // ...
    }
}

object MyApp {
    def main(args: Array[String]): Unit = {
        val app = new MyApp
        new CommandLine(app)
            .setExecutionStrategy(app.executionStrategy)
            .execute(args: _*)
    }
}

This ensures the init method is called after the command line is parsed (so all options and positional parameters are assigned) but before the user-specified subcommand is executed.

The logging example in picocli-examples shows how this can be used to configure the Log4j log level based on a --verbose option, prior to execution.

17.7. @ParentCommand Annotation

In command line applications with subcommands, options of the top level command are often intended as "global" options that apply to all the subcommands. Prior to picocli 2.2, subcommands had no easy way to access their parent command options unless the parent command made these values available in a global variable.

The @ParentCommand annotation introduced in picocli 2.2 makes it easy for subcommands to access their parent command options: subcommand fields annotated with @ParentCommand are initialized with a reference to the parent command. For example:

Java
@Command(name = "fileutils", subcommands = ListFiles.class)
class FileUtils {

    @Option(names = {"-d", "--directory"},
            description = "this option applies to all subcommands")
    File baseDirectory;
}
Groovy
@Command(name = "fileutils", subcommands = [ListFiles.class])
class FileUtils {

    @Option(names = ["-d", "--directory"],
            description = "this option applies to all subcommands")
    def baseDirectory;
}
Kotlin
@Command(name = "fileutils", subcommands = [ListFiles::class])
class FileUtils {

    @Option(names = ["-d", "--directory"],
            description = ["this option applies to all subcommands"])
    var baseDirectory: File? = null
}
Scala
@Command(name = "fileutils", subcommands = Array(classOf[ListFiles]))
class FileUtils {

    @Option(names = Array("-d", "--directory"),
            description = Array("this option applies to all subcommands"))
    var baseDirectory: File = null
}

The above top-level command has a --directory option that applies to its subcommands. The ListFiles subcommand can use the @ParentCommand annotation to get a reference to the parent command, so it can easily access the parent command options.

Java
@Command(name = "list")
class ListFiles implements Runnable {

    @ParentCommand
    private FileUtils parent; // picocli injects reference to parent command

    @Option(names = {"-r", "--recursive"},
            description = "Recursively list subdirectories")
    private boolean recursive;

    @Override
    public void run() {
        list(new File(parent.baseDirectory, "."));
    }

    private void list(File dir) {
        System.out.println(dir.getAbsolutePath());
        if (dir.isDirectory()) {
            for (File f : dir.listFiles()) {
                if (f.isDirectory() && recursive) {
                    list(f);
                } else {
                    System.out.println(f.getAbsolutePath());
                }
            }
        }
    }
}
Kotlin
@Command(name = "list")
class ListFiles : Runnable {

    @ParentCommand
    private val parent: FileUtils? = null // picocli injects reference to parent command

    @Option(names = ["-r", "--recursive"],
            description = ["Recursively list subdirectories"])
    private var recursive = false

    override fun run() {
        list(File(parent!!.baseDirectory, "."))
    }

    private fun list(dir: File) {
        println(dir.absolutePath)
        if (dir.isDirectory) {
            for (f in dir.listFiles()) {
                if (f.isDirectory && recursive) {
                    list(f)
                } else {
                    println(f.absolutePath)
                }
            }
        }
    }
}

17.8. Subcommand Aliases

Commands may optionally define an aliases attribute to provide alternate names that will be recognized by the parser. Aliases are displayed in the default help output. For example:

Java
@Command(name = "status", aliases = {"st"}, description = "Show the working tree status.")
class GitStatus { ... }
Kotlin
@Command(name = "status", aliases = ["st"], description = ["Show the working tree status."])
class GitStatus { ... }

Would result in this help fragment:

status, st    Show the working tree status.

17.9. Inherited Command Attributes

Picocli 4.6 adds support for inheriting @Command attributes with the scope = ScopeType.INHERIT annotation. Commands with this scope have their @Command attributes copied to all subcommands (and sub-subcommands, to any level of depth).

When a subcommand specifies an explicit value in its @Command annotation, this value is used instead of the inherited value. For example:

Java
@Command(name = "app", scope = ScopeType.INHERIT,
         mixinStandardHelpOptions = true, version = "app version 1.0",
         header = "App header",
         description = "App description",
         footerHeading = "Copyright%n", footer = "(c) Copyright by the authors",
         showAtFileInUsageHelp = true)
class App implements Runnable {
    @Option(names = "-x") int x;

    public void run() { System.out.printf("Hello from app!%nx = %d%n", x); }

    @Command(header = "Subcommand header", description = "Subcommand description")
    void sub(@Option(names = "-y") int y) {
        System.out.printf("Hello app sub!%ny = %d", y);
    }
}
Kotlin
@Command(name = "app", scope = ScopeType.INHERIT,
         mixinStandardHelpOptions = true, version = ["app version 1.0"],
         header = ["App header"],
         description = ["App description"],
         footerHeading = "Copyright%n", footer = ["(c) Copyright by the authors"],
         showAtFileInUsageHelp = true)
class App : Runnable {
    @Option(names = ["-x"]) var x = 0

    override fun run() { println("Hello from app!\nx = $x") }

    @Command(header = ["Subcommand header"], description = ["Subcommand description"])
    fun sub(@Option(names = ["-y"]) y: Int) {
        println("Hello from sub!\ny = $y")
    }
}

The app command in the above example has scope = ScopeType.INHERIT, so its @Command properties are inherited by the sub subcommand.

The sub subcommand defines its own header and description, so these are not inherited from the parent command. The help message for the subcommand looks like this:

Subcommand header
Usage: app sub [-hV] [-y=<arg0>] [@<filename>...]
Subcommand description
      [@<filename>...]   One or more argument files containing options.
  -h, --help             Show this help message and exit.
  -V, --version          Print version information and exit.
  -y=<arg0>
Copyright
(c) Copyright by the authors

Note that the subcommand has inherited the mixed-in standard help options (--help and --version), the @file usage help, and the footer and footer heading. It also inherited the version string, shown when the user invokes app sub --version.

When a command has scope = INHERIT, the following attributes are copied to its subcommands:

  • all usage help attributes: description, descriptionHeading, header, headerHeading, footer, footerHeading, customSynopsis, synopsisHeading, synopsisSubcommandLabel, abbreviateSynopsis, optionListHeading, parameterListHeading, commandListHeading, exitCodeList, exitCodeListHeading, requiredOptionMarker, showDefaultValues, sortOptions, autoWidth, width, showAtFileInUsageHelp, showEndOfOptionsDelimiterInUsageHelp, and hidden

  • exit codes: exitCodeOnSuccess, exitCodeOnUsageHelp, exitCodeOnVersionHelp, exitCodeOnInvalidInput, exitCodeOnExecutionException

  • the help and version options mixed in by mixinStandardHelpOptions

  • separator between option and option parameter

  • version

  • versionProvider

  • defaultValueProvider

  • subcommandsRepeatable

  • whether this command is a helpCommand

Attributes that are not copied include:

  • command name

  • command aliases

  • options and parameters (other than the help and version options mixed in by mixinStandardHelpOptions)

  • other mixins than mixinStandardHelpOptions

  • subcommands

  • argument groups

17.10. Inherited Options

Picocli 4.3 adds support for "inherited" options. Options defined with scope = ScopeType.INHERIT are shared with all subcommands (and sub-subcommands, to any level of depth). Applications can define an inherited option on the top-level command, in one place, to allow end users to specify this option anywhere: not only on the top-level command, but also on any of the subcommands and nested sub-subcommands.

Inherited options currently cannot be used in Argument Groups. Applications that want to reuse Argument Groups across subcommands need to use Mixins. See this example for sharing an Argument Group defining global options between subcommands.

Below is an example where an inherited option is used to configure logging.

Java
@Command(name = "app", subcommands = Sub.class)
class App implements Runnable {
    private static Logger logger = LogManager.getLogger(App.class);

    @Option(names = "-x", scope = ScopeType.LOCAL) // option is not shared: this is the default
    int x;

    @Option(names = "-v", scope = ScopeType.INHERIT) // option is shared with subcommands
    public void setVerbose(boolean[] verbose) {
        // Configure log4j.
        // (This is a simplistic example: a real application may use more levels and
        // perhaps configure only the ConsoleAppender level instead of the root log level.)
        Configurator.setRootLevel(verbose.length > 0 ? Level.DEBUG : Level.INFO);
    }

    public void run() {
        logger.debug("-x={}", x);
    }
}

@Command(name = "sub")
class Sub implements Runnable {
    private static Logger logger = LogManager.getLogger(Sub.class);

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

    public void run() {
        logger.debug("-y={}", y);
    }
}
Kotlin
@Command(name = "app", subcommands = [Sub::class])
class App : Runnable {
    private val logger = LogManager.getLogger(App::class.java)

    @Option(names = ["-x"], scope = ScopeType.LOCAL) // option is not shared by default
    var x = 0

    @Option(names = ["-v"], scope = ScopeType.INHERIT) // option is shared with subcommands
    fun setVerbose(verbose: BooleanArray) {
        // Configure log4j.
        // (This is a simplistic example: a real application may use more levels and
        // perhaps configure only the ConsoleAppender level instead of the root log level.)
        Configurator.setRootLevel(if (verbose.size > 0) Level.DEBUG else Level.INFO)
    }

    override fun run() {
        logger.debug("-x={}", x)
    }
}

@Command(name = "sub")
class Sub : Runnable {
    private val logger = LogManager.getLogger(Sub::class.java)

    @Option(names = ["-y"])
    var y = 0

    override fun run() {
        logger.debug("-y={}", y)
    }
}

Users can specify the -v option on either the top-level command or on the subcommand, and it will have the same effect.

# the -v option can be specified on the top-level command
java App -x=3 -v sub -y=4

Specifying the -v option on the subcommand will have the same effect. For example:

# specifying the -v option on the subcommand also changes the log level
java App -x=3 sub -y=4 -v

Subcommands don’t need to do anything to receive inherited options, but a potential drawback is that subcommands do not get a reference to inherited options.

Subcommands that need to inspect the value of an inherited option can use the @ParentCommand annotation to get a reference to their parent command, and access the inherited option via the parent reference. Alternatively, for such subcommands, sharing options via mixins may be a more suitable mechanism.

17.11. Manually Parsing Subcommands

For the following example, we assume we created an alias git that invokes our Java application. This could also be a script or a function that calls our Java program:

alias git='java picocli.Demo$Git'

Next, we call our command with some arguments like this:

git --git-dir=/home/rpopma/picocli status -sb -uno

Where git (actually java picocli.Demo$Git) is the top-level command, followed by a global option and a subcommand status with its own options.

Setting up the parser and parsing the command line could look like this:

Java
public static void main(String... args) {
    // Set up the parser
    CommandLine commandLine = new CommandLine(new Git());

    // add subcommands programmatically (not necessary if the parent command
    // declaratively registers the subcommands via annotation)
    commandLine.addSubcommand("status",   new GitStatus())
               .addSubcommand("commit",   new GitCommit())
               // ...
               ;

    // Invoke the parseArgs method to parse the arguments
    ParseResult parsed = commandLine.parseArgs(args);
    handleParseResult(parsed);
}
Kotlin
fun main(args: Array<String>) {
    // Set up the parser
    val commandLine = CommandLine(Git())

    // add subcommands programmatically (not necessary if the parent command
    // declaratively registers the subcommands via annotation)
    commandLine.addSubcommand("status",   GitStatus())
               .addSubcommand("commit",   GitCommit())
               // ...

    // Invoke the parseArgs method to parse the arguments
    val parsed = commandLine.parseArgs(*args)
    handleParseResult(parsed)
}

The CommandLine.parseArgs method returns a ParseResult that can be queried to get information about the top-level command (the Java class invoked by git in this example). The ParseResult.subcommand() method returns a nested ParseResult if the parser found a subcommand. This can be recursively queried until the last nested subcommand was found and the ParseResult.subcommand() method returns null.

Java
private void handleParseResult(ParseResult parsed) {
    assert parsed.subcommand() != null : "at least 1 command and 1 subcommand found";

    ParseResult sub = parsed.subcommand();
    assert parsed.commandSpec().userObject().getClass() == Git.class       : "main command";
    assert    sub.commandSpec().userObject().getClass() == GitStatus.class : "subcommand";

    Git git = (Git) parsed.commandSpec().userObject();
    assert git.gitDir.equals(new File("/home/rpopma/picocli"));

    GitStatus gitstatus = (GitStatus) sub.commandSpec().userObject();
    assert  gitstatus.shortFormat              : "git status -s";
    assert  gitstatus.branchInfo               : "git status -b";
    assert !gitstatus.showIgnored              : "git status --showIgnored not specified";
    assert  gitstatus.mode == GitStatusMode.no : "git status -u=no";
}
Kotlin
private fun handleParseResult(parsed: CommandLine.ParseResult) {
    assert(parsed.subcommand() != null) { "at least 1 command and 1 subcommand found" }

    val sub = parsed.subcommand()
    assert(parsed.commandSpec().userObject().javaClass == Git::class.java) { "main command" }
    assert(sub.commandSpec().userObject().javaClass == GitStatus::class.java) { "subcommand" }

    val git = parsed.commandSpec().userObject() as Git
    assert(git.gitDir == File("/home/rpopma/picocli"))

    val gitstatus = sub.commandSpec().userObject() as GitStatus
    assert(gitstatus.shortFormat)               { "git status -s" }
    assert(gitstatus.branchInfo)                { "git status -b" }
    assert(!gitstatus.showIgnored)              { "git status --showIgnored not specified" }
    assert(gitstatus.mode === GitStatusMode.no) { "git status -u=no" }
}

You may be interested in the execute method to reduce error handling and other boilerplate code in your application. See also the section, Executing Subcommands.

17.12. Nested sub-Subcommands

When registering subcommands declaratively, subcommands can be nested by specifying the subcommands attribute on subcommand classes:

Java
@Command(name = "main", subcommands = {
    ChildCommand1.class,
    ChildCommand2.class,
    ChildCommand3.class })
class MainCommand { }

@Command(name = "cmd3", subcommands = {
    GrandChild3Command1.class,
    GrandChild3Command2.class,
    GrandChild3Command3.class })
class ChildCommand3 { }

@Command(name = "cmd3sub3", subcommands = {
    GreatGrandChild3Command3_1.class,
    GreatGrandChild3Command3_2.class })
class GrandChild3Command3 { }
// ...
Kotlin
@Command(name = "main", subcommands = [
    ChildCommand1::class,
    ChildCommand2::class,
    ChildCommand3::class]
)
class MainCommand { }

@Command(name = "cmd3", subcommands = [
    GrandChild3Command1::class,
    GrandChild3Command2::class,
    GrandChild3Command3::class]
)
class ChildCommand3 { }

@Command(name = "cmd3sub3", subcommands = [
    GreatGrandChild3Command3_1::class,
    GreatGrandChild3Command3_2::class]
)
class GrandChild3Command3 {}
// ...

When registering subcommands programmatically, the object passed to the addSubcommand method can be an annotated object or a CommandLine instance with its own nested subcommands. For example:

Java
CommandLine commandLine = new CommandLine(new MainCommand())
    .addSubcommand("cmd1",                 new ChildCommand1())
    .addSubcommand("cmd2",                 new ChildCommand2())
    .addSubcommand("cmd3", new CommandLine(new ChildCommand3())
        .addSubcommand("cmd3sub1",                 new GrandChild3Command1())
        .addSubcommand("cmd3sub2",                 new GrandChild3Command2())
        .addSubcommand("cmd3sub3", new CommandLine(new GrandChild3Command3())
            .addSubcommand("cmd3sub3sub1", new GreatGrandChild3Command3_1())
            .addSubcommand("cmd3sub3sub2", new GreatGrandChild3Command3_2())
        )
    );
Kotlin
val commandLine = CommandLine(MainCommand())
    .addSubcommand("cmd1",             ChildCommand1())
    .addSubcommand("cmd2",             ChildCommand2())
    .addSubcommand("cmd3", CommandLine(ChildCommand3())
        .addSubcommand("cmd3sub1",             GrandChild3Command1())
        .addSubcommand("cmd3sub2",             GrandChild3Command2())
        .addSubcommand("cmd3sub3", CommandLine(GrandChild3Command3())
            .addSubcommand("cmd3sub3sub1", GreatGrandChild3Command3_1())
            .addSubcommand("cmd3sub3sub2", GreatGrandChild3Command3_2())
        )
    )

By default, the usage help message only shows the subcommands of the specified command, and not the nested sub-subcommands. This can be customized by specifying your own IHelpSectionRenderer for the command list section. The picocli-examples module has an example that shows how to accomplish this.

To get usage help for nested subcommands, either set mixinStandardHelpOptions = true in all commands in the hierarchy, and/or add picocli.CommandLine.HelpCommand to the subcommands of all commands. Then you can get help message for a nested (sub-)subcommand like this:

$ main cmd3 cmd3sub3 cmd3sub3sub1 --help      # mixinStandardHelpOptions
Usage: main cmd3 cmd3sub3 cmd3sub3sub1 [-hV] [-y=<y>] ...
...
$ main cmd3 cmd3sub3 help cmd3sub3sub1        # HelpCommand
Usage: main cmd3 cmd3sub3 cmd3sub3sub1 [-y=<y>] ... [COMMAND]
...

17.13. Repeatable Subcommands

From picocli 4.2, it is possible to specify that a command’s subcommands can be specified multiple times by marking it with @Command(subcommandsRepeatable = true).

17.13.1. Example

Below is an example where the top-level command myapp is marked as subcommandsRepeatable = true. This command has three subcommands, add, list and send-report:

Java
@Command(name = "myapp", subcommandsRepeatable = true)
class MyApp implements Runnable {

    @Command
    void add(@Option(names = "-x") String x, @Option(names = "-w") double w) { ... }

    @Command
    void list(@Option(names = "--where") String where) { ... }

    @Command(name = "send-report")
    void sendReport(@Option(names = "--to", split = ",") String[] recipients) { ... }

    public static void main(String... args) {
        new CommandLine(new MyApp()).execute(args);
    }
}
Kotlin
@Command(name = "myapp", subcommandsRepeatable = true)
class MyApp : Runnable {

    @Command
    fun add(@Option(names = ["-x"]) x: String, @Option(names = ["-w"]) w: Double) { ... }

    @Command
    fun list(@Option(names = ["--where"]) where: String) { ... }

    @Command(name = "send-report")
    fun sendReport(@Option(names = ["--to"], split = ",") recipients: Array<String>) { ... }

    companion object {
        @JvmStatic
        fun main