= picocli - a mighty tiny command line interface //:author: Remko Popma //:email: rpopma@apache.org :revnumber: 4.6.1 :revdate: 2021-01-03 :toc: left :numbered: :toclevels: 2 :toc-title: Features :source-highlighter: rouge :rouge-languages: kotlin, groovy, scala //:source-highlighter: highlightjs //:highlightjs-theme: darkula :icons: font :imagesdir: images :sectanchors: :linkattrs: :docinfo: shared-head,private ifdef::env-github[] :caution-caption: :fire: :important-caption: :heavy_exclamation_mark: :note-caption: :information_source: :tip-caption: :bulb: :warning-caption: :warning: endif::[] [link=https://github.com/remkop/picocli] image::https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png[Fork me on GitHub,float="right"] ++++ ++++ [quote] Every main method deserves picocli! image:logo/horizontal.png[picocli the Mighty Tiny Command Line Interface,width=800] The user manual for the latest release is at http://picocli.info. For the busy and impatient: there is also a link:quick-guide.html[Quick Guide]. == 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 https://github.com/remkop/picocli/wiki/Feedback-from-Users[what happy users say] about picocli. === 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 <> to contrast important elements and reduce the cognitive load on the user. Picocli-based applications can have link:autocomplete.html[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 image:https://www.graalvm.org/resources/img/logo-colored.svg[GraalVM] <>, with extremely fast startup time and lower memory requirements, which can be distributed as a single executable file. Picocli <> for your application (HTML, PDF and Unix man pages). .An example usage help message with ANSI colors and styles image:checksum-usage-help.png[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 https://clig.dev/#guidelines[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. TIP: Picocli also provides a <>, separately from the annotations API. === Example application The example below shows a short but fully functional picocli-based `checksum` command line application. // NOTE: other documents reference the [[CheckSum-application]] link; do not change it .Java [[CheckSum-application]] [source,java,role="primary"] ---- 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 (MD5 by default) of a file to STDOUT.") class CheckSum implements Callable { @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 = "MD5"; @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); } } ---- // Note: this example deliberately does not have the callouts and detailed explanation of the Quick Guide; // in this document we want to keep the introduction section very light-weight and high-level. // The goal of the introduction is to show how easy picocli is. // (The rest of this document has plenty of details.) .Groovy [[CheckSum-App-Groovy]] [source,groovy,role="secondary"] ---- @Grab('info.picocli:picocli-groovy:4.6.1') 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 (MD5 by default) of a file to STDOUT.') class Checksum implements Callable { @Parameters(index = '0', description = 'The file whose checksum to calculate.') File file @Option(names = ['-a', '--algorithm'], description = 'MD5, SHA-1, SHA-256, ...') String algorithm = 'MD5' 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 [[CheckSum-App-Groovy-Script]] [source,groovy,role="secondary"] ---- @Grab('info.picocli:picocli-groovy:4.6.1') 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 (MD5 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 = 'MD5' println MessageDigest.getInstance(algorithm).digest(file.bytes).encodeHex().toString() ---- .Kotlin [[CheckSum-App-Kotlin]] [source,kotlin,role="secondary"] ---- 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 (MD5 by default) of a file to STDOUT."]) class Checksum : Callable { @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 = "MD5" 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) : Unit = exitProcess(CommandLine(Checksum()).execute(*args)) ---- .Scala [[CheckSum-App-Scala]] [source,scala,role="secondary"] ---- 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 (MD5 by default) of a file to STDOUT.")) class Checksum extends Callable[Int] { @Parameters(index = "0", description = Array("The file whose checksum to calculate.")) private val file: File = null @Option(names = Array("-a", "--algorithm"), description = Array("MD5, SHA-1, SHA-256, ...")) private var algorithm = "MD5" 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: _*)) } } ---- [TIP] ==== You can https://www.jdoodle.com/embed/v0/2mQM?stdin=1&arg=1[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 <> 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 <> attribute adds `--help` and `--version` options to your application. [TIP] ==== The picocli Quick Guide shows other https://picocli.info/quick-guide.html#_example_applications[sample applications] and explains them in more detail. ==== == Getting Started You can add picocli as an external dependency to your project, or you can include it as source. === Add as External Dependency Below are examples of configuring Gradle or Maven to use picocli as an external dependency in your project: .Gradle [source,groovy,role="primary"] ---- dependencies { implementation 'info.picocli:picocli:4.6.1' } ---- .Maven [source,xml,role="secondary"] ---- info.picocli picocli 4.6.1 ---- === Add as Source To include as source, get the source code from the link:https://github.com/remkop/picocli/blob/master/src/main/java/picocli/CommandLine.java[GitHub file]. Copy and paste it into a file called `CommandLine.java`, add it to your project, and enjoy! === 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. * **<>**. The annotation processor generates and updates https://github.com/oracle/graal/blob/master/substratevm/BuildConfiguration.md[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 https://github.com/oracle/graal/blob/master/substratevm/Reflection.md[reflection], https://github.com/oracle/graal/blob/master/substratevm/Resources.md[resources] and https://github.com/oracle/graal/blob/master/substratevm/DynamicProxy.md[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. ==== Processor option: `project` The picocli annotation processor supports a number of https://github.com/remkop/picocli/tree/master/picocli-codegen#picocli-processor-options[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=` to the javac compiler. The examples below show how to do this for Maven and Gradle. ==== Enabling the Annotation Processor ===== IDE https://immutables.github.io/apt.html[This page] shows the steps to configure Eclipse and IntelliJ IDEA to enable annotation processing. ===== Using Build Tools .Gradle [source,groovy,role="primary"] ---- dependencies { implementation 'info.picocli:picocli:4.6.1' annotationProcessor 'info.picocli:picocli-codegen:4.6.1' } compileJava { options.compilerArgs += ["-Aproject=${project.group}/${project.name}"] } ---- .Maven [source,xml,role="secondary"] ---- org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin-version} info.picocli picocli-codegen 4.6.1 -Aproject=${project.groupId}/${project.artifactId} ---- See the https://github.com/remkop/picocli/tree/master/picocli-codegen[`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 (https://kotlinlang.org/docs/reference/kapt.html[kapt]), then replace `annotationProcessor` with `kapt`: ``` apply plugin: 'kotlin-kapt' // required dependencies { // ... kapt 'info.picocli:picocli-codegen:4.6.1' } ``` And replace `compileJava.options.compilerArgs` with `kapt.arguments`: ``` kapt { arguments { arg("project", "${project.group}/${project.name}") } } ``` See the https://github.com/remkop/picocli/tree/master/picocli-codegen[`picocli-codegen` README] for more details. === Running the Application After we successfully compiled our <>, 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: [source,bash] ---- 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 [OPTIONS]`. For example: [source,bash] ---- java -cp "picocli-4.6.1.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: [source,bash] ---- checksum --algorithm SHA-1 hello.txt ---- See the <> section for ideas on how to accomplish this. == 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. image:OptionsAndParameters2.png[Example command with annotated @Option and @Parameters] Picocli has separate annotations for options and positional parameters. === 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 <>. TIP: You may be interested in this http://catb.org/~esr/writings/taoup/html/ch10s05.html#id2948149[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 <> option. .Java [source,java,role="primary"] ---- 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 ore more files to archive") File[] files; @Option(names = { "-h", "--help" }, usageHelp = true, description = "display a help message") private boolean helpRequested = false; } ---- .Kotlin [source,kotlin,role="secondary"] ---- 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 ore more files to archive"]) lateinit var files: Array; @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 [source,java,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- 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("file1.txt"), File("file2.txt")))) ---- === 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 https://docs.oracle.com/javase/8/docs/api/java/io/Console.html#readPassword-java.lang.String-java.lang.Object...-[`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. CAUTION: 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. ==== 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 [source,java,role="primary"] ---- class Login implements Callable { @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 [source,kotlin,role="secondary"] ---- class Login : Callable { @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 [source,java,role="primary"] ---- new CommandLine(new Login()).execute("-u", "user123", "-p"); ---- .Kotlin [source,kotlin,role="secondary"] ---- CommandLine(Login()).execute("-u", "user123", "-p") ---- Then the user will be prompted to enter a value: [source] ---- 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: [source] ---- Hi user123, your passphrase is hashed to 75K3eLr+dx6JJFuJ7LwIpEpOFmwGZZkRiB84PURz6U8=. ---- ==== 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 <> 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 <>.) For example, if an application has these options: .Java [source,java,role="primary"] ---- @Option(names = "--user") String user; @Option(names = "--password", arity = "0..1", interactive = true) char[] password; ---- .Kotlin [source,kotlin,role="secondary"] ---- @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 ---- [TIP] .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 https://github.com/remkop/picocli/blob/master/picocli-examples[`picocli-examples`] module has an example, coded both in https://github.com/remkop/picocli/blob/master/picocli-examples/src/main/java/picocli/examples/interactive/PasswordDemo.java[Java] and https://github.com/remkop/picocli/tree/master/picocli-examples/src/main/kotlin/picocli/examples/kotlin/interactive/PasswordDemo.kt[Kotlin]. ==== [CAUTION] .Interactive options and shell applications with JLine 2 ==== Interactive options do not work in conjunction with JLine 2's `ConsoleReader`. Either implement a <> which uses JLine2's `ConsoleReader` directly or use https://github.com/remkop/picocli/tree/master/picocli-shell-jline3[`picocli-shell-jline3`]. ==== === Short (POSIX) Options Picocli supports http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02[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 [source,java,role="primary"] ---- class ClusteredShortOptions { @Option(names = "-a") boolean aaa; @Option(names = "-b") boolean bbb; @Option(names = "-c") boolean ccc; @Option(names = "-f") String file; } ---- .Kotlin [source,kotlin,role="secondary"] ---- 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: [source,bash] ---- -abcfInputFile.txt -abcf=InputFile.txt -abc -f=InputFile.txt -ab -cf=InputFile.txt -a -b -c -fInputFile.txt -a -b -c -f InputFile.txt -a -b -c -f=InputFile.txt ... ---- === Boolean Options Boolean options usually don't need a parameter: it is enough to specify the option name on the command line. .Java [source,java,role="primary"] ---- class BooleanOptions { @Option(names = "-x") boolean x; } ---- .Kotlin [source,kotlin,role="secondary"] ---- 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 <> 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 <>) is assigned a value, as follows: * If the parser is configured to <>, the opposite of the _current value_ is assigned. (This was the default prior to picocli 4.0.) * If a <> is defined, the fallback value is assigned. * If the option is defined with a non-zero <>, and an option parameter was specified on the command line, this option <>. * Otherwise, the value assigned is the logical opposite of the _default value_. === Negatable Options From picocli 4.0, boolean options can be `negatable`. .Java [source,java,role="primary"] ---- @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 [source,kotlin,role="secondary"] ---- @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 <>.) If the negated form of the option is found, for example `--no-verbose`, the value is set to the provided default. Otherwise, with a regular call, for example `--verbose`, it is set to the opposite of the default. [TIP] .Negatable options that are `true` by default ==== When a negatable option is `true` by default, give it the negative name. For example: .Java [source,java,role="primary"] ---- @Option(names = "--no-backup", negatable = true, description = "Make a backup. True by default.") boolean backup = true; ---- .Kotlin [source,kotlin,role="secondary"] ---- @Option(names = ["--no-backup"], negatable = true, description = ["Make a backup. True by default."]) var backup = true ---- When end users specify `--no-backup` on the command line, the value is set to `false`. The negated form of this option is `--backup`, and if that is specified, the default value is applied. ==== === 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. ==== 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 [source,java,role="primary"] ---- 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 allParameters; // no "index" attribute: captures _all_ arguments } ---- .Kotlin [source,kotlin,role="secondary"] ---- class PositionalParameters { @Parameters(index = "0") lateinit var host: InetAddress @Parameters(index = "1") var port = 0 @Parameters(index = "2..*") lateinit var files: Array @Parameters(hidden = true) // "hidden": don't show this parameter in usage help message lateinit var allParameters: List // no "index" attribute: captures _all_ arguments } ---- Picocli initializes fields with the values at the specified index in the arguments array. .Java [source,java,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- 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 <> for which types are supported out of the box and how to add custom types. ==== 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. [CAUTION] ==== 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 <> is the safer option. (Multi-value positional parameters can safely omit the `index` attribute.) ==== IMPORTANT: Methods cannot be iterated over in predictable order. For applications with <> or combinations of `@Parameters`-annotated methods and `@Parameters`-annotated fields, we recommend using <> for single-value positional parameters. See <> for details. === 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 [source,java,role="primary"] ---- class Mixed { @Parameters List positional; @Option(names = "-o") List options; } ---- .Kotlin [source,kotlin,role="secondary"] ---- class Mixed { @Parameters lateinit var positional: List @Option(names = ["-o"]) lateinit var options: List } ---- Any command line argument that is not an option or subcommand is interpreted as a positional parameter. .Java [source,java,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- 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")) ---- === Double dash (`--`) 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 [source,java,role="primary"] ---- class DoubleDashDemo { @Option(names = "-v") boolean verbose; @Option(names = "-files") List files; @Parameters List params; } ---- .Kotlin [source,kotlin,role="secondary"] ---- class DoubleDashDemo { @Option(names = ["-v"]) var verbose = false @Option(names = ["-files"]) var files: List? = null @Parameters lateinit var params: List } ---- The `--` end-of-options delimiter clarifies which of the arguments are positional parameters: .Java [source,java,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- 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 <> for details. [[AtFiles]] === @-files ==== 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`. 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: [source,bash] ---- java MyCommand @/home/foo/args ---- The above will be expanded to the contents of the file: [source,bash] ---- java MyCommand ABC -option=123 "X Y Z" ---- @-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 https://docs.oracle.com/en/java/javase/14/docs/specs/man/java.html#java-command-line-argument-files[java Command-Line Argument Files]. [TIP] ==== 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 https://github.com/remkop/picocli/blob/master/picocli-examples/src/main/java/picocli/examples/atfile/AtFileGenerator.java[example] to get you started. ==== ==== @-files Usage Help From picocli 4.2, an entry for `@` can be shown in the options and parameters list of the usage help message of a command with the `@Command(showAtFileInUsageHelp = true)` annotation. See <> for details. ==== 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 http://jcommander.org/#_syntax[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. == 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. === 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.TimeZoneConverter` (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`) TIP: 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`. === Custom Type Converters Register a custom type converter to handle data types other than the above built-in ones. Custom converters need to implement the `picocli.CommandLine.ITypeConverter` interface: [source,java] ---- public interface ITypeConverter { /** * 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; } ---- Custom type converters can be registered with the `CommandLine.registerConverter(Class cls, ITypeConverter converter)` method. All options and positional parameters with the specified type will be converted by the specified converter. NOTE: Java 8 lambdas make it easy to register custom converters: .Java [source,java,role="primary"] ---- CommandLine cl = new CommandLine(app); cl.registerConverter(Locale.class, s -> new Locale.Builder().setLanguageTag(s).build()); cl.registerConverter(Cipher.class, s -> Cipher.getInstance(s)); ---- .Kotlin [source,java,role="secondary"] ---- val cl = CommandLine(app) cl.registerConverter(Locale::class.java) { s: String? -> Locale.Builder().setLanguageTag(s).build() } cl.registerConverter(Cipher::class.java) { Cipher.getInstance(it) } ---- 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 [source,java,role="primary"] ---- class App { @Parameters java.util.Locale locale; @Option(names = "-a") javax.crypto.Cipher cipher; } ---- .Kotlin [source,kotlin,role="secondary"] ---- import java.util.Locale import javax.crypto.Cipher // ... class App { @Parameters lateinit var locale: Locale @Option(names = ["-a"]) lateinit var cipher: Cipher } ---- .Java [source,kotlin,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- 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") ---- CAUTION: _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. === 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 [source,java,role="primary"] ---- import java.net.InetSocketAddress; class InetSocketAddressConverter implements ITypeConverter { @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 [source,kotlin,role="secondary"] ---- import java.net.InetSocketAddress // ... class InetSocketAddressConverter : ITypeConverter { 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 https://github.com/remkop/picocli/blob/master/picocli-examples/src/main/java/picocli/examples/typeconverter/InetSocketAddressConverterDemo.java[example] which you can run in our https://www.jdoodle.com/embed/v0/2mxo?stdin=1&arg=1[online-editor^]. === 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 https://docs.oracle.com/javase/9/docs/api/java/sql/Types.html[`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 [source,java,role="primary"] ---- class App { @Option(names = "--sqlType", converter = SqlTypeConverter.class) int sqlType; } ---- .Kotlin [source,kotlin,role="secondary"] ---- class App { @Option(names = ["--sqlType"], converter = [SqlTypeConverter::class]) var sqlType = 0 } ---- Example implementation: .Java [source,java,role="primary"] ---- class SqlTypeConverter implements ITypeConverter { 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 [source,kotlin,role="secondary"] ---- class SqlTypeConverter : ITypeConverter { @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 <> is installed to instantiate classes. === Arrays, Collections, Maps NOTE: 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.) ==== 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 <> is registered. For example: .Java [source,java,role="primary"] ---- 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 files; // picocli infers type from the generic type } ---- .Kotlin [source,kotlin,role="secondary"] ---- import java.io.File import java.util.regex.Pattern // ... class Convert { @Option(names = ["-patterns"], description = ["the regex patterns to use"]) lateinit var patterns: Array @Parameters( /* type = [File::class], */ description = ["the files to convert"]) lateinit var files: List // picocli infers type from the generic type } ---- .Java [source,java,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- 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 ---- NOTE: 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`, `OrderedSet -> 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 <> section for details. ==== Maps Picocli 1.0 introduced support for `Map` fields similar to Java's system properties `-Dkey=value` or Gradle's project properties `-Pmyprop=myvalue`. `Map` fields may have any type for their key and value as long as a <> 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 [source,java,role="primary"] ---- import java.net.InetAddress; import java.net.Proxy.Type; import java.util.concurrent.TimeUnit; class MapDemo { @Option(names = {"-p", "--proxyHost"}) Map proxies; @Option(names = {"-u", "--timeUnit"}) Map timeout; } ---- .Kotlin [source,kotlin,role="secondary"] ---- import java.net.InetAddress import java.net.Proxy import java.util.concurrent.TimeUnit // ... class MapDemo { @Option(names = ["-p", "--proxyHost"]) lateinit var proxies: Map @Option(names = ["-u", "--timeUnit"]) lateinit var timeout: Map } ---- Map options may be specified multiple times with different key-value pairs. (See <>.) [source,bash] ---- -p HTTP=123.123.123.123 --proxyHost SOCKS=212.212.212.212 -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. NOTE: On the command line, the key and the value must be separated by a `=` 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 <> section for details. ==== 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 a `=` 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 to specify only a key. The value type can be <, wrapped in a `java.util.Optional`>>. For example: .Java [source,java,role="primary"] ---- @Option(names = {"-P", "--properties"}, mapFallbackValue = Option.NULL_VALUE) Map> properties; @Parameters(mapFallbackValue = "INFO", description = "... ${MAP-FALLBACK-VALUE} ...") Map, LogLevel> logLevels; ---- .Kotlin [source,kotlin,role="secondary"] ---- @Option(names = ["-P", "--properties"], mapFallbackValue = Option.NULL_VALUE) lateinit var properties: Map> @Parameters(mapFallbackValue = "INFO", description = "... ${MAP-FALLBACK-VALUE} ...") lateinit var logLevels: Map, LogLevel> ---- This allows input like the following: ---- --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 <<_predefined_variables,`${MAP-FALLBACK-VALUE}`>> variable which will be replaced with the actual map fallback value when the usage help is shown. ==== 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`-<> that delegates all key-value pairs to `System::setProperty`. Note the use of `mapFallbackValue = ""` to allow <>. .Java [source,java,role="primary"] ---- class SystemPropertiesDemo { @Option(names = "-D", mapFallbackValue = "") // allow -Dkey void setProperty(Map props) { props.forEach((k, v) -> System.setProperty(k, v)); } } ---- .Kotlin [source,kotlin,role="secondary"] ---- class SystemPropertiesDemo { @Option(names = ["-D"], mapFallbackValue = "") // allow -Dkey fun setProperty(props: Map) { props.forEach { (k: String, v: String) -> System.setProperty(k, v) } } } ---- === Optional 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 [source,java,role="primary"] ---- @Option(names = "-x") Optional x; @Option(names = "-D", mapFallbackValue = Option.NULL_VALUE) Map> map; ---- .Kotlin [source,kotlin,role="secondary"] ---- @Option(names = ["-x"]) lateinit var x: Optional @Option(names = ["-D"], mapFallbackValue = Option.NULL_VALUE) lateinit var map: Map> ---- WARNING: 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. === 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 [source,java,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- class App { @Option(names = ["--big"], type = [BigDecimal::class]) // concrete Number subclass lateinit var big: Array // array type with abstract component class @Option(names = ["--small"], type = [Short::class]) // other Number subclass lateinit var small: Array @Parameters(type = [StringBuilder::class]) // StringBuilder implements CharSequence lateinit var address: CharSequence // interface type } ---- ==== 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` or `Map`, 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 [source,java,role="primary"] ---- 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 stronglyTyped; @Option(names = "-s", type = CharBuffer.class) List text; } ---- .Kotlin [source,kotlin,role="secondary"] ---- 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 @Option(names = ["-s"], type = [CharBuffer::class]) lateinit var text: List } ---- === 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 <> 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`. == 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. [#defaultValue-annotation] === `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 <> and <>, there is no alternative but to use the `defaultValue` annotation attribute. For example, for an annotated interface: .Java [source,java,role="primary"] ---- interface Spec { @Option(names = "-c", defaultValue = "123", description = "... ${DEFAULT-VALUE} ...") int count(); } ---- .Kotlin [source,kotlin,role="secondary"] ---- 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 [source,java,role="primary"] ---- class CommandMethod { @Command(description = "Do something.") void doit(@Option(names = "-c", defaultValue = "123") int count) { // ... } } ---- .Kotlin [source,kotlin,role="secondary"] ---- class CommandMethod { @Command(description = ["Do something."]) fun doit(@Option(names = ["-c"], defaultValue = "123") count: Int) { // ... } } ---- Note that you can use the `${DEFAULT-VALUE}` <> in the `description` of the option or positional parameter and picocli will <> the actual default value. === Field Values For annotated fields, it is possible to declare the field with a value: .Java [source,java,role="primary"] ---- @Option(names = "-c", description = "The count (default: ${DEFAULT-VALUE})") int count = 123; // default value is 123 ---- .Kotlin [source,kotlin,role="secondary"] ---- @Option(names = ["-c"], description = ["The count (default: \${DEFAULT-VALUE})"]) var count = 123 // default value is 123 ---- [WARNING] ==== 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 <> * 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. ==== === Default Provider Finally, you can specify a default provider in the `@Command` annotation: .Java [source,java,role="primary"] ---- @Command(defaultValueProvider = MyDefaultProvider.class) class MyCommand // ... ---- .Kotlin [source,kotlin,role="secondary"] ---- @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: [source,java] ---- 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 https://github.com/remkop/picocli/blob/master/picocli-examples/src/main/java/picocli/examples/defaultprovider/[default provider examples] for example implementations. === 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, 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 [source,java,role="primary"] ---- import picocli.CommandLine.PropertiesDefaultProvider; // ... @Command(name = "git", defaultValueProvider = PropertiesDefaultProvider.class) class Git { } ---- .Kotlin [source,kotlin,role="secondary"] ---- 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. The location of the properties file may also be specified programmatically. For example: .Java [source,java,role="primary"] ---- CommandLine cmd = new CommandLine(new MyCommand()); File defaultsFile = new File("path/to/config/mycommand.properties"); cmd.setDefaultValueProvider(new PropertiesDefaultProvider(defaultsFile)); cmd.execute(args); ---- .Kotlin [source,kotlin,role="secondary"] ---- val cmd = CommandLine(MyCommand()) val defaultsFile = File("path/to/config/mycommand.properties") cmd.defaultValueProvider = PropertiesDefaultProvider(defaultsFile) cmd.execute(*args) ---- ==== PropertiesDefaultProvider Format The `PropertiesDefaultProvider` expects the properties file to be in the standard java `.properties` https://en.wikipedia.org/wiki/.properties[format]. For options, the key is either the https://picocli.info/apidocs/picocli/CommandLine.Option.html#descriptionKey--[descriptionKey], or the option's https://picocli.info/apidocs/picocli/CommandLine.Model.OptionSpec.html#longestName--[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 https://picocli.info/apidocs/picocli/CommandLine.Parameters.html#descriptionKey--[descriptionKey], or the positional parameter's https://picocli.info/apidocs/picocli/CommandLine.Parameters.html#paramLabel--[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. ==== 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: [source] ---- # /home/remko/.git.properties git.commit.cleanup = strip ---- [#fallbackValue-annotation] === `fallbackValue` Annotation If an option is defined with `arity = "0..1"`, it <>. 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 [source, java,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- 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) { CommandLine(FallbackValueDemo()).execute(*args) } ---- Gives the following results: [source, bash] ---- 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 <> if necessary. Note that the option description may contain the `${FALLBACK-VALUE}` <> which will be replaced with the actual fallback value when the usage help is shown. <> 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. === 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 [source,java,role="primary"] ---- @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 [source,kotlin,role="secondary"] ---- @Command(name = "defaults", mixinStandardHelpOptions = true, version = ["defaults 0.1"]) class DefaultValueDemo : 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()} (arg value or default)") println() } } } fun main(args: Array) { CommandLine(DefaultValueDemo()).execute(*args) } ---- == Multiple Values Multi-valued options and positional parameters are annotated fields that can capture multiple values from the command line. === Multiple Occurrences ==== 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 [source,java,role="primary"] ---- @Option(names = "-option") int[] values; ---- .Kotlin [source,kotlin,role="secondary"] ---- @Option(names = ["-option"]) lateinit var values: IntArray ---- Users may specify the same option multiple times. For example: ---- -option 111 -option 222 -option 333 ---- Each value is appended to the array or collection. ==== Multiple Positional Parameters Similarly for multi-valued positional parameters: .Java [source,java,role="primary"] ---- @Parameters List units; ---- .Kotlin [source,kotlin,role="secondary"] ---- @Parameters lateinit var units: List ---- Users may specify multiple positional parameters. For example: ---- SECONDS HOURS DAYS ---- Again, each value is appended to the array or collection. ==== Repeated Boolean Options Boolean options with multiple values are supported as of picocli 2.1.0. .Java [source,java,role="primary"] ---- @Option(names = "-v", description = { "Specify multiple -v options to increase verbosity.", "For example, `-v -v -v` or `-vvv`"}) boolean[] verbosity; ---- .Kotlin [source,kotlin,role="secondary"] ---- @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: ---- -v -v -v -vvv ---- The above example results in six `true` values being added to the `verbosity` array. === 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 <>. .Java [source,java,role="primary"] ---- @Option(names = "-option", split = ",") int[] values; ---- .Kotlin [source,kotlin,role="secondary"] ---- @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 <>: .Java [source,java,role="primary"] ---- @Option(names = "-fix", split = "\\|", splitSynopsisLabel = "|") Map message; ---- .Kotlin [source,kotlin,role="secondary"] ---- @Option(names = ["-fix"], split = "\\|", splitSynopsisLabel = "|") lateinit var message: Map ---- 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 <> 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 <> for details. === 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 [source,java,role="primary"] ---- 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 [source,kotlin,role="secondary"] ---- class ArityDemo { @Parameters(arity = "1..3", description = ["one to three Files"]) lateinit var files: Array @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 } ---- 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. === Variable Arity Limitations ==== Variable Arity Options and Unknown Options As mentioned in the <>, while processing parameters for an option with variable arity, when a known option, a subcommand, or the <> is encountered, picocli will stop adding parameters to the variable arity option. However, by default the picocli parser does not give special treatment to <> (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 <