Picocli is a small Java library that makes it easy to build command-line tools. You write plain Java classes, add a few annotations, and Picocli handles parsing, help text, and error messages for you.
Why build a CLI tool?
Many useful tools run in the terminal:
- Build scripts
- Data migration utilities
- Dev helpers (rename files, check configs, seed databases)
- Internal admin tools
You can parse args manually with String[] args, but that gets messy quickly when you need flags, subcommands, defaults, and --help.
Picocli solves that. It is popular, well documented, and works nicely with Java projects.
What is Picocli?
Picocli is an open-source Java framework for building command-line interfaces.
With Picocli you can:
- Define options like
--name Sivaor-n Siva - Define positional arguments like a file path
- Create subcommands like
git commitormytool export - Auto-generate help and version output
- Show clear errors when the user types invalid input
You describe your CLI in Java. Picocli does the parsing.
A simple example: a greeting CLI
Let us build a tiny tool called greet that says hello to someone.
1. Add the dependency
Maven:
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>4.7.6</version>
</dependency>
Gradle:
implementation 'info.picocli:picocli:4.7.6'
2. Create the command class
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
@Command(
name = "greet",
mixinStandardHelpOptions = true,
version = "greet 1.0",
description = "Prints a greeting."
)
public class GreetCommand implements Runnable {
@Option(names = {"-n", "--name"}, description = "Name to greet", defaultValue = "World")
String name;
@Option(names = {"-u", "--uppercase"}, description = "Print in uppercase")
boolean uppercase;
@Parameters(index = "0", arity = "0..1", description = "Optional extra message")
String message;
@Override
public void run() {
String text = "Hello, " + name;
if (message != null && !message.isBlank()) {
text += " — " + message;
}
if (uppercase) {
text = text.toUpperCase();
}
System.out.println(text);
}
public static void main(String[] args) {
int exitCode = new CommandLine(new GreetCommand()).execute(args);
System.exit(exitCode);
}
}
3. Run it
java GreetCommand
# Hello, World
java GreetCommand --name Siva
# Hello, Siva
java GreetCommand -n Siva -u "welcome to the blog"
# HELLO, SIVA — WELCOME TO THE BLOG
java GreetCommand --help
# Shows usage, options, and description
That is a working CLI with help text and almost no parsing code.
How Picocli maps input to your class
| User types | Picocli sets |
|---|---|
--name Siva |
name = "Siva" |
-u or --uppercase |
uppercase = true |
welcome (positional) |
message = "welcome" |
--help |
prints usage and exits |
| wrong flag | clear error message |
Picocli reads annotations on fields and methods, then fills them from args.
Common annotations
@Option
For named flags and values:
@Option(names = {"-v", "--verbose"}, description = "Show more output")
boolean verbose;
@Option(names = "--count", defaultValue = "1", description = "Repeat count")
int count;
@Parameters
For positional arguments (no flag name):
@Parameters(index = "0", description = "Input file")
Path inputFile;
@Command
On the main class or subcommand:
@Command(name = "mytool", description = "Does useful work")
Useful settings:
mixinStandardHelpOptions = true— adds--helpand--versionsubcommands = { ExportCommand.class, ImportCommand.class }— nested commands
Subcommands: one tool, many actions
Real CLIs often have subcommands. Think of docker run, docker ps, or kubectl get.
Example structure:
@Command(
name = "files",
subcommands = {
ListCommand.class,
CopyCommand.class
}
)
public class FilesApp implements Runnable {
@Override
public void run() {
CommandLine.usage(this, System.out);
}
public static void main(String[] args) {
System.exit(new CommandLine(new FilesApp()).execute(args));
}
}
@Command(name = "list", description = "List files in a folder")
class ListCommand implements Runnable {
@Parameters(index = "0", description = "Directory")
File dir;
@Override
public void run() {
File[] files = dir.listFiles();
if (files == null) {
System.err.println("Not a directory: " + dir);
return;
}
for (File f : files) {
System.out.println(f.getName());
}
}
}
Usage:
java FilesApp list /tmp
Picocli routes list to ListCommand automatically.
Validation and user-friendly errors
You can check input inside run():
@Override
public void run() {
if (count < 1) {
throw new CommandLine.ParameterException(
new CommandLine(this),
"Count must be at least 1"
);
}
// ...
}
Or use a separate validation method with @Command — Picocli also supports ITypeConverter for custom types (for example, parsing a string into an enum or Path).
Good error messages matter. A CLI that says “invalid value for option ‘–port’” is much easier to use than a stack trace.
Exit codes
CommandLine.execute(args) returns an exit code:
0— success- non-zero — failure
Shell scripts and CI pipelines use this to know if a command passed or failed.
int exitCode = new CommandLine(new GreetCommand()).execute(args);
System.exit(exitCode);
Return errors from your logic by throwing CommandLine.ExecutionException or using Picocli’s built-in exit code mapping.
Package as a runnable JAR
For a real tool, ship a fat JAR (all dependencies included) with a main class.
Maven (shade plugin snippet):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>GreetCommand</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
Then:
java -jar greet.jar --name Siva
You can also use jpackage (JDK 14+) to build a native installer or executable for Windows, macOS, or Linux.
Picocli vs manual parsing
| Approach | Pros | Cons |
|---|---|---|
Manual args parsing |
No dependency | Hard to maintain, poor help text |
| Picocli | Clean code, help, subcommands, validation | Small dependency |
For anything beyond one or two flags, Picocli is worth it.
Tips for good CLI design
- Keep commands focused — one job per subcommand.
- Use sensible defaults —
greetwithout--nameshould still work. - Write clear
--helptext — users read help first. - Print errors to stderr — use
System.errfor errors, stdout for normal output. - Return proper exit codes —
0on success, non-zero on failure. - Name flags consistently — short (
-v) and long (--verbose) forms together.
When to pick Picocli
Picocli is a great fit if you:
- Build Java CLI tools for your team or open source
- Want annotation-based, readable command definitions
- Need subcommands, tab completion, and good help output
- Prefer staying in Java without switching to Python or Go
For very large CLIs, also look at Picocli’s support for completion scripts (bash/zsh) and ANSI colors for richer terminal output.
Quick recap
- Add the Picocli dependency.
- Create a class with
@Command,@Option, and@Parameters. - Implement
Runnableand put your logic inrun(). - Call
new CommandLine(new YourCommand()).execute(args)frommain. - Package as a JAR and share your tool.
In a few dozen lines of Java, you get a proper CLI with --help, options, and clean parsing — without writing a parser yourself.