picocli
A modern, feature-rich Java CLI library. Supports annotation-based parsing, strong typing, subcommands, and automatically generated colorful help messages.
GitHub Overview
remkop/picocli
Picocli is a modern framework for building powerful, user-friendly, GraalVM-enabled command line apps with ease. It supports colors, autocompletion, subcommands, and more. In 1 source file so apps can include as source & avoid adding a dependency. Written in Java, usable from Groovy, Kotlin, Scala, etc.
Topics
Star History
Framework
picocli
Overview
picocli is a modern Java framework for building powerful, user-friendly, GraalVM-enabled command line applications with ease. With the concept of "a mighty tiny command line interface," it provides rich functionality including color output, autocompletion, and subcommands through an annotation-based declarative approach. Composed of a single source file, it can be included directly as source to avoid dependencies, and is usable not only from Java but also from Groovy, Kotlin, and Scala.
Why is it the most popular in Java:
- Complete GraalVM native image support: Achieves fast startup and low memory usage
- Annotation-driven concise syntax: Rich functionality with minimal boilerplate
- Comprehensive feature set: Supports POSIX, GNU, and MS-DOS style syntax
- Strong type safety: Automatic type conversion and validation leveraging Java's type system
Detailed Description
History and Development
picocli is a Java CLI framework developed by Remko Popma, characterized by its innovative design using an annotation-based declarative approach. Unlike traditional programmatic APIs, it enables more readable and maintainable code by defining command-line structures using @Command and @Option annotations. Development continues actively, with complete support for the latest Java features and GraalVM ecosystem.
Position in the Ecosystem
picocli holds a special position in the Java ecosystem for the following reasons:
- Most modern Java CLI library: Pioneer in annotation-driven and GraalVM support
- Migration destination from JCommander: Provides more intuitive and type-safe approach
- Cloud-native era compatibility: Optimal for cloud-native with fast startup and low resource consumption
- Enterprise-grade functionality: Rich adoption track record in large-scale applications
Latest Trends (2024-2025)
v4.7.8 (July 2025 Latest Stable Version)
- Continuous improvements: Stable release cycle with semantic versioning
- Enhanced annotation processor: Improved accuracy of compile-time error checking
- Automatic GraalVM configuration generation: Automatic generation of configuration files under META-INF/native-image
- Performance optimizations: Further improvements in startup time and memory usage
v4.7.7 (2024 Stable Version)
- ArgGroup improvements: Fixed duplicate option issues when combined with Mixins
- HelpCommand enhancements: exitCodeOnUsageHelp support with Callable
implementation - Execution strategy improvements: Prioritize call method when implementing both Callable and Runnable
Major Technical Innovations
- Annotation processor: Compile-time type checking and automatic configuration file generation
- Tracing API: Programmatic trace level setting
- Variable interpolation: Dynamic resolution of system properties, environment variables, and resource bundle keys
- Argument groups: Support for mutually exclusive/dependent options
Importance in the Cloud-Native Era (2024-2025)
- Container optimization: Dramatically reduce container size with GraalVM native images
- Serverless compatibility: Perfect for AWS Lambda and Google Cloud Functions with fast startup
- Kubernetes integration: Utilization in lightweight init containers and sidecar patterns
- CI/CD pipelines: High-performance execution in build tools and deployment scripts
Key Features
Core Functionality
- Annotation-driven: Declarative definition using @Command, @Option, @Parameters
- Rich syntax support: POSIX, GNU, and MS-DOS style command-line syntax
- Automatic type conversion: Leverages Java type system for automatic argument conversion
- Comprehensive validation: Support for arity, choices, and custom validation
- Subcommands: Hierarchical command structure and dynamic subcommand discovery
Advanced Features
- Argument groups: Grouping of mutually exclusive/dependent options
- Mixin support: Reuse of common options and modular design
- Custom help: Detailed customization of ANSI colored help messages
- Auto-completion: Tab completion script generation for Bash, Zsh, Fish
- Internationalization: Multi-language support through resource bundles
GraalVM Integration
- Native image support: Fast startup (millisecond level) and low memory usage
- Automatic configuration generation: Automatic reflection configuration generation via annotation processor
- Cross-platform: Executable file generation for Windows, Linux, macOS
- Jansi integration: Color output support on Windows
Latest Features and Updates
Annotation Processor (picocli-codegen)
- Compile-time error checking: Early detection of invalid annotations and attributes
- Automatic GraalVM configuration generation: Automatic creation of reflect-config.json, resource-config.json, proxy-config.json
- Project-specific configuration: Namespace separation of configuration files via -Aproject option
Tracing and Debugging
- Programmatic tracing: Dynamic control via CommandLine.tracer().setLevel()
- Detailed error messages: Detailed information on type conversion errors and validation failures
- Runtime diagnostics: Runtime validation and reporting of annotation configuration
Performance Improvements
- Startup time optimization: Acceleration via picocli.disable.closures system property
- char[] as single-value type: Type system improvement in 4.7.0
- Execution strategy optimization: Efficient selection between Callable and Runnable
Pros and Cons
Pros
Functionality
- Strong type safety: Automatic type conversion leveraging Java type system
- Rich functionality: Comprehensive support from basic to advanced features
- Excellent developer experience: Intuitive development through annotation-driven approach
- Complete GraalVM support: High performance with native images
Quality
- High reliability: Rich track record in enterprise environments
- Active maintenance: Continuous improvement and support for latest technologies
- Comprehensive documentation: Detailed documentation and abundant samples
- Strong test support: Easy integration with unit tests
Cons
Learning Cost Aspects
- Annotation complexity: Learning cost when using advanced features
- GraalVM-specific constraints: Need to understand limitations in native images
Performance Aspects
- JVM startup overhead: Startup time in traditional Java execution (resolved with native images)
- Memory usage: Slight memory overhead due to rich functionality
Key Links
Official Resources
Documentation
Usage Examples
Basic Usage Example
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.File;
@Command(name = "example", mixinStandardHelpOptions = true, version = "Picocli example 4.0")
public class Example implements Runnable {
@Option(names = { "-v", "--verbose" },
description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
private boolean[] verbose = new boolean[0];
@Parameters(arity = "1..*", paramLabel = "FILE", description = "File(s) to process.")
private File[] inputFiles;
public void run() {
if (verbose.length > 0) {
System.out.println(inputFiles.length + " files to process...");
}
if (verbose.length > 1) {
for (File f : inputFiles) {
System.out.println(f.getAbsolutePath());
}
}
}
public static void main(String[] args) {
// By implementing Runnable or Callable, parsing, error handling and handling user
// requests for usage help or version help can be done with one line of code.
int exitCode = new CommandLine(new Example()).execute(args);
System.exit(exitCode);
}
}
# Usage example
java Example file1.txt file2.txt -v
# Output: 2 files to process...
java Example file1.txt --verbose --verbose
# Output:
# 1 files to process...
# /path/to/file1.txt
Checksum Application
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.File;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.math.BigInteger;
import java.util.concurrent.Callable;
@Command(name = "checksum", mixinStandardHelpOptions = true, version = "Checksum 4.0",
description = "Prints the checksum (SHA-1 by default) of a file to STDOUT.")
public 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-1";
@Override
public Integer call() throws Exception {
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;
}
public static void main(String[] args) {
int exitCode = new CommandLine(new CheckSum()).execute(args);
System.exit(exitCode);
}
}
# Usage example
java CheckSum myfile.txt --algorithm=MD5
# Output: a1b2c3d4e5f6789... (MD5 hash)
java CheckSum myfile.txt -a SHA-256
# Output: 9f86d0818cf7... (SHA-256 hash)
Detailed Option and Parameter Definition
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.File;
import java.util.List;
@Command(name = "encrypt",
description = "Encrypt FILE(s), or standard input, to standard output or to the output file.",
version = "Encrypt version 1.0",
footer = "Copyright (c) 2017",
exitCodeListHeading = "Exit Codes:%n",
exitCodeList = {
" 0:Successful program execution.",
"64:Invalid input: an unknown option or invalid parameter was specified.",
"70:Execution exception: an exception occurred while executing the business logic."
})
public class Encrypt {
@Parameters(paramLabel = "FILE", description = "Any number of input files")
private List<File> files = new ArrayList<File>();
@Option(names = { "-o", "--out" }, description = "Output file (default: print to console)")
private File outputFile;
@Option(names = { "-v", "--verbose"},
description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
private boolean[] verbose;
@Option(names = { "-c", "--cipher" },
description = "Encryption algorithm: ${COMPLETION-CANDIDATES}",
completionCandidates = CipherCandidates.class)
private String cipher = "AES";
@Option(names = { "-p", "--password" },
description = "Encryption password",
interactive = true,
arity = "0..1")
private char[] password;
static class CipherCandidates extends ArrayList<String> {
CipherCandidates() { super(Arrays.asList("AES", "DES", "RSA")); }
}
}
Subcommand Implementation
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
@Command(name = "git",
mixinStandardHelpOptions = true,
version = "subcommand demo - picocli 4.0",
subcommands = {GitCommit.class, GitStatus.class, GitPush.class},
description = "Git is a fast, scalable, distributed revision control system")
public class Git implements Runnable {
@Option(names = "--git-dir", description = "Set the path to the repository.")
private File gitDir;
public void run() {
System.out.println("Git command executed");
}
public static void main(String[] args) {
int exitCode = new CommandLine(new Git()).execute(args);
System.exit(exitCode);
}
}
@Command(name = "commit", description = "Record changes to the repository")
class GitCommit implements Runnable {
@Option(names = {"-m", "--message"}, description = "Use the given message as the commit message")
private String message;
@Option(names = {"-a", "--all"}, description = "Automatically stage files that have been modified and deleted")
private boolean all;
public void run() {
System.out.println("Committing changes...");
if (message != null) {
System.out.println("Commit message: " + message);
}
if (all) {
System.out.println("Staging all modified files");
}
}
}
@Command(name = "status", description = "Show the working tree status")
class GitStatus implements Runnable {
@Option(names = {"-s", "--short"}, description = "Give the output in the short-format")
private boolean shortFormat;
public void run() {
System.out.println("Repository status:");
if (shortFormat) {
System.out.println("(short format)");
}
}
}
@Command(name = "push", description = "Update remote refs along with associated objects")
class GitPush implements Runnable {
@Parameters(index = "0", description = "The remote repository")
private String remote = "origin";
@Parameters(index = "1", description = "The branch to push")
private String branch = "main";
@Option(names = {"-f", "--force"}, description = "Force push")
private boolean force;
public void run() {
System.out.println("Pushing to " + remote + "/" + branch);
if (force) {
System.out.println("Force push enabled");
}
}
}
Argument Groups (Mutually Exclusive Options)
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.ArgGroup;
@Command(name = "exclusive", description = "Demonstrates mutually exclusive options")
public class ExclusiveOptions implements Runnable {
@ArgGroup(exclusive = true, multiplicity = "1")
Exclusive exclusive;
static class Exclusive {
@Option(names = "-a", description = "Option A")
boolean a;
@Option(names = "-b", description = "Option B")
boolean b;
@Option(names = "-c", description = "Option C")
boolean c;
}
@ArgGroup(exclusive = false, multiplicity = "1")
Dependent dependent;
static class Dependent {
@Option(names = "--user", required = true, description = "User name")
String user;
@Option(names = "--password", required = true, description = "Password")
String password;
}
public void run() {
System.out.println("Exclusive option selected");
System.out.println("User: " + dependent.user);
}
public static void main(String[] args) {
new CommandLine(new ExclusiveOptions()).execute(args);
}
}
# Usage example (success)
java ExclusiveOptions -a --user=john --password=secret
# Usage example (error: mutually exclusive options)
java ExclusiveOptions -a -b --user=john --password=secret
# Error: Error: -a, -b are mutually exclusive (specify only one)
Common Option Reuse with Mixins
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Mixin;
// Mixin defining common options
class CommonOptions {
@Option(names = {"-v", "--verbose"}, description = "Verbose output")
boolean verbose;
@Option(names = {"-o", "--output"}, description = "Output file")
File outputFile;
}
// Database-related common options
class DatabaseOptions {
@Option(names = {"--host"}, description = "Database host", defaultValue = "localhost")
String host;
@Option(names = {"--port"}, description = "Database port", defaultValue = "5432")
int port;
@Option(names = {"--database"}, description = "Database name")
String database;
}
@Command(name = "backup", description = "Backup database")
class BackupCommand implements Runnable {
@Mixin CommonOptions commonOptions;
@Mixin DatabaseOptions dbOptions;
@Option(names = {"--compress"}, description = "Compress backup file")
boolean compress;
public void run() {
if (commonOptions.verbose) {
System.out.println("Backing up database " + dbOptions.database);
System.out.println("Host: " + dbOptions.host + ":" + dbOptions.port);
}
System.out.println("Creating backup...");
if (compress) {
System.out.println("Compressing backup file");
}
if (commonOptions.outputFile != null) {
System.out.println("Output file: " + commonOptions.outputFile);
}
}
}
@Command(name = "restore", description = "Restore database")
class RestoreCommand implements Runnable {
@Mixin CommonOptions commonOptions;
@Mixin DatabaseOptions dbOptions;
@Option(names = {"--backup-file"}, required = true, description = "Backup file to restore")
File backupFile;
public void run() {
if (commonOptions.verbose) {
System.out.println("Restoring database " + dbOptions.database);
}
System.out.println("Restoring from: " + backupFile);
}
}
Advanced Validation and Custom Type Conversion
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.ITypeConverter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.regex.Pattern;
// Custom type converter
class LocalDateConverter implements ITypeConverter<LocalDate> {
public LocalDate convert(String value) throws Exception {
return LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE);
}
}
// Custom validator
class EmailValidator implements IParameterConsumer {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$");
public void consumeParameters(Stack<String> args, ArgSpec argSpec, CommandSpec commandSpec) {
String email = args.pop();
if (!EMAIL_PATTERN.matcher(email).matches()) {
throw new ParameterException(commandSpec.commandLine(),
"Invalid email format: " + email);
}
argSpec.setValue(email);
}
}
@Command(name = "register", description = "User registration command")
public class RegisterCommand implements Runnable {
@Option(names = {"--email"},
description = "User email address",
parameterConsumer = EmailValidator.class,
required = true)
private String email;
@Option(names = {"--birthdate"},
description = "Birth date (YYYY-MM-DD)",
converter = LocalDateConverter.class)
private LocalDate birthDate;
@Option(names = {"--age"},
description = "Age (must be between 13 and 120)")
@Range(min = 13, max = 120)
private int age;
@Parameters(index = "0",
description = "Username (3-20 characters, alphanumeric only)")
@Pattern(regexp = "^[a-zA-Z0-9]{3,20}$",
message = "Username must be 3-20 alphanumeric characters")
private String username;
public void run() {
System.out.println("Registering user: " + username);
System.out.println("Email: " + email);
System.out.println("Age: " + age);
if (birthDate != null) {
System.out.println("Birth date: " + birthDate);
}
}
public static void main(String[] args) {
new CommandLine(new RegisterCommand()).execute(args);
}
}
GraalVM Native Image Configuration
// CheckSum.java - GraalVM compatible sample application
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.File;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.concurrent.Callable;
@Command(name = "checksum",
mixinStandardHelpOptions = true,
version = "checksum 4.0",
description = "Calculate file checksum")
public class CheckSum implements Callable<Integer> {
@Parameters(index = "0", description = "The file whose checksum to calculate.")
private File file;
@Option(names = {"-a", "--algorithm"},
description = "Hash algorithm: MD5, SHA-1, SHA-256, etc.",
defaultValue = "SHA-1")
private String algorithm;
@Override
public Integer call() throws Exception {
byte[] fileContents = Files.readAllBytes(file.toPath());
byte[] digest = MessageDigest.getInstance(algorithm).digest(fileContents);
StringBuilder result = new StringBuilder();
for (byte b : digest) {
result.append(String.format("%02x", b));
}
System.out.println(result.toString());
return 0;
}
public static void main(String[] args) {
int exitCode = new CommandLine(new CheckSum()).execute(args);
System.exit(exitCode);
}
}
<!-- pom.xml - Maven annotation processor configuration -->
<project>
<properties>
<picocli.version>4.7.8-SNAPSHOT</picocli.version>
</properties>
<dependencies>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>${picocli.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>info.picocli</groupId>
<artifactId>picocli-codegen</artifactId>
<version>${picocli.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Aproject=checksum</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
// build.gradle - Gradle annotation processor configuration
plugins {
id 'java'
id 'com.palantir.graal' version '0.10.0'
}
dependencies {
implementation 'info.picocli:picocli:4.7.8-SNAPSHOT'
annotationProcessor 'info.picocli:picocli-codegen:4.7.8-SNAPSHOT'
}
compileJava {
options.compilerArgs += ['-Aproject=checksum']
}
graal {
outputName 'checksum'
mainClass 'CheckSum'
option '--static'
option '--no-server'
}
# Building GraalVM Native Image
# 1. Compile application (annotation processor automatically generates config files)
javac -cp picocli-4.7.8-SNAPSHOT.jar CheckSum.java
# 2. Create JAR file
jar -cfe checksum.jar CheckSum *.class
# 3. Verify JAR contents (check auto-generated config files)
jar -tf checksum.jar
# META-INF/native-image/picocli-generated/
# META-INF/native-image/picocli-generated/proxy-config.json
# META-INF/native-image/picocli-generated/reflect-config.json
# META-INF/native-image/picocli-generated/resource-config.json
# 4. Build native image
native-image -cp picocli-4.7.8-SNAPSHOT.jar --static -jar checksum.jar
# 5. Performance comparison
time java -cp checksum.jar CheckSum myfile.txt
# real 0m0.500s (JVM startup overhead)
time ./checksum myfile.txt
# real 0m0.005s (native image fast startup)
Internationalization and Localization
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import java.util.ResourceBundle;
@Command(name = "i18n-example",
resourceBundle = "messages",
description = "%command.description")
public class I18nExample implements Runnable {
@Option(names = {"-l", "--language"},
description = "%option.language.description")
private String language = "en";
@Option(names = {"-f", "--file"},
description = "%option.file.description")
private String filename;
public void run() {
ResourceBundle bundle = ResourceBundle.getBundle("messages",
java.util.Locale.forLanguageTag(language));
System.out.println(bundle.getString("greeting"));
if (filename != null) {
System.out.println(bundle.getString("processing.file") + ": " + filename);
}
}
public static void main(String[] args) {
new CommandLine(new I18nExample()).execute(args);
}
}
# messages_en.properties
command.description=Internationalization example
option.language.description=Language code (en, ja, de)
option.file.description=File to process
greeting=Hello, World!
processing.file=Processing file
# messages_ja.properties
command.description=国際化のサンプル
option.language.description=言語コード (en, ja, de)
option.file.description=処理するファイル
greeting=こんにちは、世界!
processing.file=ファイルを処理中
# messages_de.properties
command.description=Internationalisierungsbeispiel
option.language.description=Sprachcode (en, ja, de)
option.file.description=Zu verarbeitende Datei
greeting=Hallo, Welt!
processing.file=Verarbeite Datei
Test Integration
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import picocli.CommandLine;
import java.io.File;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Files;
import static org.junit.jupiter.api.Assertions.*;
public class CheckSumTest {
@Test
public void testBasicUsage(@TempDir Path tempDir) throws Exception {
// Create test file
File testFile = tempDir.resolve("test.txt").toFile();
Files.write(testFile.toPath(), "Hello, World!".getBytes());
// Execute command
CheckSum checkSum = new CheckSum();
CommandLine cmd = new CommandLine(checkSum);
StringWriter sw = new StringWriter();
cmd.setOut(new PrintWriter(sw));
int exitCode = cmd.execute(testFile.getAbsolutePath());
// Verify results
assertEquals(0, exitCode);
String output = sw.toString().trim();
assertFalse(output.isEmpty());
assertEquals(40, output.length()); // SHA-1 is 40 characters
}
@Test
public void testInvalidFile() {
CheckSum checkSum = new CheckSum();
CommandLine cmd = new CommandLine(checkSum);
StringWriter sw = new StringWriter();
cmd.setErr(new PrintWriter(sw));
int exitCode = cmd.execute("nonexistent.txt");
// Verify error is handled properly
assertNotEquals(0, exitCode);
String errorOutput = sw.toString();
assertTrue(errorOutput.contains("nonexistent.txt"));
}
@Test
public void testVersionOption() {
CheckSum checkSum = new CheckSum();
CommandLine cmd = new CommandLine(checkSum);
StringWriter sw = new StringWriter();
cmd.setOut(new PrintWriter(sw));
int exitCode = cmd.execute("--version");
assertEquals(0, exitCode);
String output = sw.toString();
assertTrue(output.contains("checksum 4.0"));
}
@Test
public void testHelpOption() {
CheckSum checkSum = new CheckSum();
CommandLine cmd = new CommandLine(checkSum);
StringWriter sw = new StringWriter();
cmd.setOut(new PrintWriter(sw));
int exitCode = cmd.execute("--help");
assertEquals(0, exitCode);
String output = sw.toString();
assertTrue(output.contains("Usage:"));
assertTrue(output.contains("algorithm"));
}
}
Programmatic API
import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Model.PositionalParamSpec;
public class ProgrammaticExample {
public static void main(String[] args) {
// Build command specification programmatically
CommandSpec spec = CommandSpec.create()
.name("dynamic-command")
.version("1.0")
.addOption(OptionSpec.builder("-v", "--verbose")
.description("Enable verbose output")
.type(boolean.class)
.build())
.addOption(OptionSpec.builder("-f", "--file")
.description("Input file")
.type(File.class)
.required(true)
.build())
.addPositional(PositionalParamSpec.builder()
.index("0..*")
.description("Additional arguments")
.type(String[].class)
.build());
// Define runtime processing
spec.addSubcommand("process", CommandSpec.create()
.addOption(OptionSpec.builder("--format")
.description("Output format")
.type(String.class)
.defaultValue("json")
.build()));
CommandLine cmd = new CommandLine(spec);
// Custom ExecutionStrategy
cmd.setExecutionStrategy(new CommandLine.IExecutionStrategy() {
public int execute(CommandLine.ParseResult parseResult) throws CommandLine.ExecutionException {
System.out.println("Custom execution strategy");
// Get parsed values
boolean verbose = parseResult.matchedOptionValue("-v", false);
File file = parseResult.matchedOptionValue("-f", null);
if (verbose) {
System.out.println("Processing file: " + file);
}
return 0;
}
});
// Execute
int exitCode = cmd.execute(args);
System.exit(exitCode);
}
}
As demonstrated, picocli is a comprehensive framework that supports powerful CLI application development in Java, from basic usage to advanced features. Its integration with GraalVM is particularly noteworthy, enabling fast startup and low memory usage that was difficult to achieve with traditional Java applications.