How to build a CLI app in Java using jbang and picocli
Tags: java, cli
Published: 2020-08-17 14:27:47

---
title: How to build a CLI app in Java using jbang and picocli
published: true
date: 17-08-2020
tags: java, cli
canonical_url: https://www.twilio.com/blog/cli-app-java-jbang-picocli
---

Traditionally Java applications have been used for long-running processes - web application servers can run for days or weeks at a time. The JVM handles this well: [Garbage Collection](https://www.baeldung.com/jvm-garbage-collectors) is efficient over huge amounts of memory, and [Profile-Guided Optimization](https://en.wikipedia.org/wiki/Profile-guided_optimization) can make your code faster the longer it runs.

However, it’s perfectly possible to write short-lived apps too, and in this post I’ll show how to build a CLI app whose total runtime is just a couple of seconds. You can build sophisticated CLI tools in Java for data processing, connection to databases, [fetching data from the web](https://dev.to/twilio/5-ways-to-make-http-requests-in-java-2p3), or taking advantage of any of the Java libraries that you're used to.

I’ll use [jbang](https://jbang.dev) for packaging and running the app, and [picocli](https://picocli.info/) to handle argument parsing and output.  The app will send an SMS using [Twilio’s Messaging API](https://www.twilio.com/docs/sms/api), in a single Java source file less than 100 lines long.

To follow along you will need:

- [SDKMAN!](https://sdkman.io/) - I wrote about [why I love SDKMAN!](https://dev.to/twilio/using-sdkman-to-work-with-multiple-versions-of-java-54km) Before.
- a Twilio account. If you don’t have one already, get one for free at [https://twilio.com/try-twilio](https://twilio.com/try-twilio).
- your Twilio account credentials set as [environment variables](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html): `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN`.

## ❗ Jbang

[Jbang](https://jbang.dev/) is a one-stop shop for making Java applications that can be run as scripts. It can download the required version of Java, add dependencies and create self-executing source files. It can also run Java code directly from the web, from github repos or even from Twitter (if you can fit your code into [280 characters](https://twitter.com/maxandersen/status/1266329490927616001)).

Using SDKMAN! install jbang with: `sdk install jbang`.

_For the impatient: Once you have jbang installed you can skip the rest of this post and run the code directly from GitHub with `jbang https://github.com/mjg123/SendSmsJbang/blob/master/SendSms.java` - the first time you run it, you will be reminded that running code from URLs is inherently unsafe - you can trust me here though_ 😉

To create a new script for a CLI app, in an empty directory run:

```
jbang init --template=cli SendSms.java
```

This creates a single file, `SendSms.java` which you can run directly with `./SendSms.java`. This file is valid both as Java source and as a shell script, thanks to the first line:

```
//usr/bin/env jbang "$0" "$@" ; exit $?
```

This is a neat trick that works as Java since it’s a comment. As a shell script it runs `jbang`, passing the name of the source file and any arguments.

The second line shows how to add dependencies to a jbang script:

```
//DEPS info.picocli:picocli:4.2.0
```

This is the standard `GROUP_ID:ARTIFACT_ID:VERSION` format for dependencies, used by gradle and other build tools.

The rest of the file is normal Java code which is the “Hello World” for picocli, a library for creating CLI apps in Java.

The last thing to do with jbang is to create a project which works in an IDE. You can’t just open the `SendSms.java` file because jbang needs to download dependencies and add them to the classpath.

Find out how to launch your IDE from the command line. For me on Linux it’s [`idea.sh`](https://intellij-support.jetbrains.com/hc/en-us/articles/360011901879-How-to-start-IDE-from-the-command-line) - for you it might be `idea` or `eclipse` or `code` or something else, depending on which IDE you like and how you installed it. The following command will create a project that your IDE can understand, open the IDE and then monitor the project for changes:

```
jbang edit --live=<YOUR IDE COMMAND> SendSms.java
```

This command will wait indefinitely, watching for changes to the project. For testing the app as you are working, I find it easiest to have another open terminal in the same directory. Make sure to open this _after_ installing jbang to make sure that it is on your `$PATH`. Now let's get into the code.

## ⌨️ Picocli

[Picocli](https://picocli.info/) is a library for creating CLI apps in Java. The goal for today is to modify the existing `SendSms.java` so that it actually sends SMS. The arguments to the script will be:

- the `to` phone number (probably your cell number)
- the `from` number (your Twilio number)
- the message body can be specified at the end of the command

The phone numbers should always be in [E.164 format](https://www.twilio.com/docs/glossary/what-e164). Running the script would look like this:

```
./SendSms.java \
    --to +4477xxxxxx46 \
    --from +1528xxxxx734 \
    Ahoy there 👋
```

If the message isn't at the end of the command, it will be read from standard in, which means you can use the script in a [pipeline](https://en.wikipedia.org/wiki/Pipeline_(Unix)) like this:

```
fortune | ./SendSms.java --to +4477xxxxxx46 --from +1528xxxxx734 
```

([fortune](https://en.wikipedia.org/wiki/Fortune_(Unix)) is a UNIX command that prints "funny" quotes)

### ⁉️ Command line Options and Parameters

Picocli distinguishes between **options**  and **parameters** :

- Options have names, specified by flags, and can appear in any order: `--to` and `--from` are options.
- Parameters are positional, the words of the message will be parameters if they're given at the end of the command.

Delete the `private String greeting` and its annotation, and add parameters and options for `--to` `--from` and the message:

```java
@CommandLine.Option(
    names = {"-t", "--to"},
    description = "The number you're sending the message @|bold to|@",
    required = true)
private String toPhoneNumber;

@CommandLine.Option(
    names = {"-f", "--from"},
    description = "The number you're sending the message @|bold from|@",
    required = true)
private String fromPhoneNumber;

@Parameters(
    index = "0",
    description = "The message to send",
    arity = "0..\*")
private String[] message;
```
[[this code on GitHub](https://github.com/mjg123/SendSmsJbang/blob/master/SendSms.java#L20-L37)]

The first two highlighted lines include text surrounded by `@|bold … |@` which tells picocli to [format](https://picocli.info/#_ansi_colors_and_styles) the words `to` and `from`.

For the `message` we specify the arity as `0..*` which means "zero or more parameters", which will be gathered into an array of Strings. Note that if there are zero then `message` will be `null` rather than an empty array.

### 🚪 About exit codes

Skip over the `main` method - the action happens in the `call` method, which returns an `Integer` used as the script's [exit code](https://www.shellscript.sh/exitcodes.html). An exit code of `0` means "success" and any other number means "failure". This script will exit with `1` for any kind of error. There are a few [exit codes with special meanings](https://tldp.org/LDP/abs/html/exitcodes.html), but none of those will apply to this script.

### 📩 Building the message text

If the message is given at the end of the command then it will be in the `String[] message`. If not, the array will be `null` and we need to read from [Standard Input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)) (stdin). Picocli doesn't have anything for reading from stdin but you can do it using a [`Scanner`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Scanner.html).

Remove the code from the `call()` method and replace it with this:

```java
String wholeMessage;

if (message != null) {
  wholeMessage = String.join(" ", message);
} else {
  var scanner = new Scanner(System.in).useDelimiter("\\A");
  wholeMessage = "";
  if (scanner.hasNext()) {
    wholeMessage = scanner.next();
  }
}

if (wholeMessage.isBlank()){
  printlnAnsi("@|red You need to provide a message somehow|@");
  return 1;
}
```
[[this code on GitHub](https://github.com/mjg123/SendSmsJbang/blob/master/SendSms.java#L51-L67)]

If the message is non-null, join the parts together with spaces, otherwise use a `Scanner` to read from stdin. Using a delimiter of `\\A` with a `Scanner` will read the whole input as a single token - known as the [Stupid Scanner Trick](https://community.oracle.com/blogs/pat/2004/10/23/stupid-scanner-tricks) since at least 2004.

Finally, if there isn't a message from either source, print an error and exit with a `1`.  I added a `printlnAnsi` method above `call()` for including formatted output - it looks like this:

```java
private void printlnAnsi(String msg) {
  System.out.println(CommandLine.Help.Ansi.AUTO.string(msg));
}
```
[[this code on GitHub](https://github.com/mjg123/SendSmsJbang/blob/master/SendSms.java#L44-L46)]

### 📲 Sending the SMS

Below the `call()` method, add this `sendSMS` method:

```java
private void sendSMS(String to, String from, String wholeMessage) {

  Twilio.init(
    System.getenv("TWILIO_ACCOUNT_SID"),
    System.getenv("TWILIO\_AUTH\_TOKEN"));

  Message.creator(
    new PhoneNumber(to),
    new PhoneNumber(from),
    wholeMessage)
  .create();
}
```

[[this code on GitHub](https://github.com/mjg123/SendSmsJbang/blob/master/SendSms.java#L82-L92)]

This is all that's needed to send an SMS with Twilio. For it to work we need to add a dependency on the Twilio Java Helper Library, so add this line to the top of the file, underneath the picocli dependency:

```java
//DEPS com.twilio.sdk:twilio:7.54.2
```

### 🛠️ Joining it up

The last thing to do is call `sendSMS` from `call`. Adding this code a the end of the `call` method will do just that:

```java
try {
  System.out.print("Sending to ..." + toPhoneNumber + ": ");
  sendSMS(toPhoneNumber, fromPhoneNumber, wholeMessage);
  printlnAnsi("@|green OK|@");

} catch (Exception e) {
  printlnAnsi("@|red FAILED|@");
  printlnAnsi("@|red " + e.getMessage() + "|@");
  return 1;
}

return 0;
```
[[this code on GitHub](https://github.com/mjg123/SendSmsJbang/blob/master/SendSms.java#L68-L79)]

All being well, this will send the SMS. Any exception, such as bad credentials or using a non-existent phone number will put us in the `catch` block where we print the error in red and exit with a `1`.

That's it! You can run [the final script](https://github.com/mjg123/SendSmsJbang/blob/master/SendSms.java) with:

```
./SendSms.java --to <YOUR CELL NUMBER> --from <YOUR TWILIO NUMBER> AHOY-HOY
```

...and the message will arrive.

## 🎁 Wrapping up

We've seen that Java isn't just useful for big web applications - it works just fine for short-running CLI applications too, made easier with jbang and picocli.  This was just an example though - Twilio already has a very useful [CLI](https://www.twilio.com/docs/twilio-cli/quickstart) already which can send SMS and do a lot more besides.

Just for fun I did also create [a version of the code that would fit in a tweet](https://twitter.com/MaximumGilliard/status/1287845230197211137).

I'd love to hear about the fun (or serious) CLI tools you're building with Java, don't worry if they're more than 280 characters. Find me on Twitter or by email:

🐦 [@MaximumGilliard](https://twitter.com/maximumgilliard?lang=el)

📧 [mgilliard@twilio.com](mailto:mgilliard@twilio.com)

I can't wait to see what you build!