CLI Tools
Turn a Java class into a real command-line tool — read arguments and stdin, return exit codes, use environment variables, and graduate to picocli and a runnable jar.
Learn CLI Tools in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You should be comfortable with methods and arrays, and have seen exception handling (parsing text can throw). To package the final tool, the Deployment lesson covers building a jar.
Think of a command-line tool as a kitchen appliance . The arguments you type are the dials you set before turning it on ( --count 3 ). Stdin is the food you feed in through the chute. The result comes out one spout ( stdout ) while error noises come out another ( stderr ). And when it finishes, a light shows green for success or red for a jam — that light is the exit code .
You can wire all of that by hand with raw args[] — like building the appliance from loose parts. picocli is the pre-built appliance: you label the dials with annotations and it handles parsing, validation, and the --help manual for you.
Every Java program starts at one method: public static void main(String[] args) . The args array holds whatever words you typed after the program name. If you run java Greet Alice 3 , then args[0] is "Alice" and args[1] is "3" .
Two things trip everyone up. First, every argument arrives as a String — "3" is text, so convert it with Integer.parseInt before doing maths. Second, args can be empty , so always check args.length before reading args[0] .
Stdin ("standard input") is the stream of text a user types — or that another program pipes in with | . Two classes read it. Scanner is friendly for interactive prompts ( nextLine , nextInt ). BufferedReader is faster and ideal when lots of lines are piped in.
The key pattern: readLine() returns the next line, or null when input ends (the user presses Ctrl-D , or the pipe finishes). Looping while ((line = in.readLine()) != null) processes every line and stops cleanly.
When your tool finishes, it returns an exit code to the shell: 0 means success, any non-zero number means failure. Scripts read it (the shell stores it in $? ) to decide whether to keep going. Set it with System.exit(code) , and send error text to System.err , not System.out .
Configuration comes from two places. Environment variables are set by the shell before your program runs — read them with System.getenv("HOME") (returns null if unset). System properties are JVM settings you pass with -D , like java -Denv=prod App , read with System.getProperty("env", "dev") where "dev" is the fallback.
Hand-rolled args[] parsing works for tiny scripts, but it gets ugly fast: options with values, flags, defaults, type conversion, --help text, and subcommands all become your problem. picocli is a small, popular library that does all of that from annotations.
You annotate fields: @Parameters marks a positional argument ( greet Alice ), @Option marks a named option ( --count 3 or the flag --upper ), and mixinStandardHelpOptions = true adds --help and --version for free. Your class implements Callable<Integer> ; picocli parses the arguments, converts "3" into an int , runs your call() method, and uses its return value as the exit code.
For bigger tools, picocli also supports subcommands — @Command(subcommands = {' UserAdd.class, UserList.class '}) gives you a git -style mytool user add / mytool user list hierarchy, each with its own options and help.
To share your tool, bundle it into a runnable jar — a single file whose manifest records which class has main . Anyone with a JDK can then run it without compiling.
For an even faster, JVM-free binary, compile the jar with GraalVM's native-image tool — it produces a single executable that starts in milliseconds. That is covered in the Deployment lesson.
You can now build a real Java command-line tool: read args[] from main , pull input from stdin with Scanner or BufferedReader , return meaningful exit codes via System.exit , and read configuration from environment variables and -D system properties. You also know when to graduate from hand-rolled parsing to picocli , and how to package the result as a runnable jar.
Next up: Clean Architecture — designing maintainable, testable applications with SOLID and DDD.
Practice quiz
In public static void main(String[] args), what is args[0] when you run 'java Greet Alice 3'?
- "Greet"
- "3"
- "Alice"
- The program name
Answer: "Alice". args holds the words AFTER the program name, so args[0] is "Alice" and args[1] is "3".
Every command-line argument arrives in main as:
- A String
- An int
- An Object
Answer: A String. All args are Strings. Convert text like "3" with Integer.parseInt before doing arithmetic.
Why must you check args.length before reading args[0]?
- args is never empty
If no arguments are passed, args has length 0 and reading args[0] throws ArrayIndexOutOfBoundsException.
BufferedReader.readLine() returns what value at the end of input?
- An empty string
- null
- -1
- It throws EOFException
Answer: null. readLine() returns null at end-of-input, which is why the loop is while ((line = in.readLine()) != null).
By convention, what exit code means SUCCESS?
- 0
- 1
- -1
- 2
Answer: 0. 0 means success; any non-zero value means failure. 2 is commonly a usage/bad-arguments error.
Error messages from a CLI tool should be sent to:
- System.out
- A log file only
- System.err
- System.getenv
Answer: System.err. Errors go to System.err so a user can redirect results (System.out) to a file while still seeing errors.
How do you read an environment variable like HOME?
- System.getProperty("HOME")
- System.getenv("HOME")
Answer: System.getenv("HOME"). System.getenv("HOME") reads an environment variable (set by the shell); it returns null if unset.
You run 'java -Denv=prod App'. How do you read that value inside the program?
- System.getenv("env")
A -D flag sets a JVM system property, read with System.getProperty("env") (with an optional fallback).
When should you graduate from hand-parsing args[] to a library like picocli?
picocli replaces brittle parsing with annotations and gives you validation, --help, and type conversion for free.
What does Integer.parseInt(args[0]) throw when the user types 'abc'?
- ArrayIndexOutOfBoundsException
- NullPointerException
- NumberFormatException
- Nothing — it returns 0
Answer: NumberFormatException. parseInt throws NumberFormatException on non-numeric text; wrap it in try/catch and print a usage message.