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.