How to build native CLI apps using Java, Maven, GraalVM, Picocli, JReleaser and GitHub actions

·
Published in
·
7 min read
·
Jan 11, 2022
19
2
A reference implementation that you can use as a jump start project
I have seen several traditional Unix command-line tools being re-written in Rust recently. Being a fan of Java, I started looking at the tooling available to build cross-platform Command Line applications in Java. Based on my research and learning, I see the following toolset being a great combination to quickly build CLI tools that are lean and performant. In the process, I built a JWT CLI utility using
- Java
- Maven for build automation
- GraalVM Native Images that are cross platform and significantly improved performance
- Picocli — a nifty little library that simplifies CLI applications
- JReleaser to manage multi-platform releases seamlessly
- GitHub actions / workflow to automate all your CI/CD workflows
In addition, I built integrations with the following to provide a view of code quality and related metrics that are essential for code that is maintainable.
- CodeCov for Code coverage analysis
- CodeClimate for automated quality analytics
- SonarCloud for automated Code Review, Testing and Inspection
- Snyk for vulnerability assessment and software composition analysis
- GitPod setup for a ready to code development environment
GitHub Repo: https://github.com/rrajesh1979/ref-java-jwt

In this blog, I detail my learnings and how you can go about building your own.
Building JWT CLI
I use IntelliJ for development. You can use your favourite IDE.
Start by setting up a new Maven project


IntelliJ uses the bundled Maven by default. If you have a more recent version of Maven installed, you can configure the same.

Install Maven Wrapper
Maven wrapper is an excellent utility that makes it easy to build our code on any machine including CI/CD servers. And we can enforce usage of specific Maven vesion.

Add LICENSE file
Add a new file in your GitHub repo. Name it as LICENSE. GitHub will prompt you to select a license template.


Configure Maven to include the License file in your packaged application. And to validate all your source code includes a Copyright header.
Add .gitignore file
Here is what I have used as a common template across projects.
Here is the core Java program to Encode and Decode JWT. I know there are more efficient ways to do this. I have focussed on the overall tooling to build a CLI application.
The JWTUtil has two main methods — createJWT and decodeJWT. And I have used JJWT — a pure Java library that simplifies creating and verifying JSON Web Tokens (JWTs) on the JVM.
Picocli — to efficiently build a JWT CLI app
I then used Picocli to build the CLI traits.
Picocli aims to be the easiest way to create rich command line applications that can run on and off the JVM. You can build Java command line applications with almost zero code. It supports a variety of command line syntax styles including POSIX, GNU, MS-DOS and more. It generates highly customizable usage help messages that use ANSI colors and styles to contrast important elements and reduce the cognitive load on the user. Picocli-based applications can have command line TAB completion showing available options, option parameters and subcommands, for any level of nested subcommands. Finally, Picocli generates beautiful documentation for your application (HTML, PDF and Unix man pages).
With a few lines of code, you can build a CLI app that behaves as shown below.



I segregated the code into 3 files to keep it maintainable — one for the main command and one for each of the sub-commands to encode and decode.
JWTC is the main Command class.
JWTCEncode and JWTCDecode are the sub-command implementations.
GraalVM Native Image
While this gives me a good command line utility built using Java and Picocli, Java applications are generally hard to distribute. You either end up requiring JVM installed in the target system. Or end up with a significantly heavy deployment package. Running inside a JVM comes with startup and footprint costs.
GraalVM solves these problems with its native images for existing JVM-based applications. The native image generation process employs static analysis to find any code reachable from the main Java method and then performs full ahead-of-time (AOT) compilation. The resulting native binary contains the whole program in machine code form for its immediate execution that is faster and leaner requiring fewer compute resources.
Maven profile to build native image using GraalVM native-image-plugin is shown below.
The following command converts the existing application into a native image.
Native Image has partial support for reflection and needs to know ahead-of-time the reflectively accessed program elements.
Native Image tries to resolve the target elements through a static analysis that detects calls to the Reflection API. Where the analysis fails, the program elements reflectively accessed at runtime must be specified using a manual configuration.
Picocli-based applications can be ahead-of-time compiled to a native image, with extremely fast startup time and lower memory requirements, which can be distributed as a single executable file. The picocli-codegen module contains an annotation processor that generates the necessary configuration files under META-INF/native-image/picocli-generated/$project during compilation, to be included in the application jar. By embedding these configuration files, your jar is instantly Graal-enabled: the native-image tool used to generate the native executable can get the configuration from the jar. In most cases, no further configuration is needed when generating a native image.
For other libraries, we need to configure manually. For example, the JJWT library that I used, requires manual configuration. Here is a simple approach that I learnt.
Build the native-image using the following Maven configuration
<buildArg>--allow-incomplete-classpath</buildArg>
When you run the resulting application, the application will report the Class files it is unable to find — as shown below.
You can then add them to the reflect-config.json in the resources/META-INF/native-image folder of your project.
And you specify the configuration in native-image.properties for GraalVM native-image-plugin to refer the reflect-config.json while building the native image.
-H=ReflectionConfigurationResources=${.}/reflect-config.json
My reflect-config.json looks as follows.
Note: For additional performance, native images can be built with profile guided optimizations gathered in a previous run of the application.
Cross platform releases with JReleaser and Maven Assembly plugin
Once you have native image available, we need to package and distribute it in respective platforms and stores.
I have used JReleaser along with Maven Assembly plugin to achieve this.
The Assembly Plugin for Maven enables developers to combine project output into a single distributable archive that also contains dependencies, modules, site documentation, and other files.
The Maven Assembly plugin configuration I used is below.
I then built operating system specific distributions using the following profile.
Finally, I used JReleaser to package and release to GitHub, HomeBrew. JReleaser also supports SKDMAN, JBang and many others.
The jreleaser-maven-plugin configuration and jreleaser.yml configuration I used are below.
Automating your CI/CD pipeline using GitHub actions
Now the final piece in the puzzle. How can you set up a CI/CD pipeline that automates the complete process above?
GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. You can build, test, and deploy your code right from GitHub. You can kick off workflows with GitHub events like push to a branch. Hosted runners for every major OS make it easy to build and test all your projects.

I used the following GitHub workflow configuration

Installation and usage
Now that you have completed the release, time to test the CLI utility. I have a Mac. And this is how I can install and test the utility.
Performance
For CLI applications, performance is paramount. And GraalVM native images provide significantly better performance and startup times. GraalVM takes over the compilation of Java bytecode to machine code. The compiler of GraalVM provides performance advantages for highly abstracted programs due to its ability to remove costly object allocations in many scenarios.
A simple comparison of the JWT encode using GraalVM vs traditional JAR is shown below.
Closing thoughts
While there is a lot of upfront configuration involved, once you set this right with all the tooling, it will significantly improve your productivity to build CLI tools.
Better yet, you can fork my repo to build your own CLI application.
Look forward to hearing your experience as well and feedback.
References
- Build Great Native CLI Apps in Java with Graalvm and Picocli: https://www.infoq.com/articles/java-native-cli-graalvm-picocli/
- https://twitter.com/aalmiray/status/1455082180120649730
- 🧸 kcctl — Your Cuddly CLI for Apache Kafka Connect: https://github.com/kcctl/kcctl. This is an excellent command line client for Kafka Connect by Gunnar Morling, Andres Almiray and team — that uses many of the tools here. This served as a great learning as well for me. Especially to build the Maven assembly and GitHub actions.
- https://twitter.com/gunnarmorling/status/1455095907582611457
19
2

Written by Rajesh Rajagopalan
·Editor for
Learner for life
More from Rajesh Rajagopalan and PeerIslands Engineering Blogs


in
Reshaping Software Development powered by Peer.AI
Future of Software Development powered by AI Agents
4 min read·Apr 23, 2024
4

in
MongoDB Authentication : AWS IAM
When using MongoDB on AWS, it is recommended to use AWS IAM to manage access to MongoDB resources. This can be done through the use of IAM…
5 min read·Mar 14, 2023
1


in
DocumenDB 2 Mongo : Migration Approach
There are several reasons why a user might consider migrating from Amazon DocumentDB to MongoDB Atlas.
10 min read·Feb 5, 2023
3


in
Extending OpenAI GPT-4 using LangChain and Pinecone for Q&A over your own content using
Using LangChain to orchestrate process to get answer for a user query from OpenAI GPT-4 using in-context learning
7 min read·May 1, 2023
28
See all from Rajesh Rajagopalan
See all from PeerIslands Engineering Blogs
Recommended from Medium


How an empty S3 bucket can make your AWS bill explode
Imagine you create an empty, private AWS S3 bucket in a region of your preference. What will your AWS bill be the next morning?
4 min read·Apr 30, 2024
10.1K


in
The beginning of the end for Terraform?
Entering the graveyard of good software that is IBM…
5 min read·Apr 26, 2024
802
Lists



Staff Picks



Stories to Help You Level-Up at Work



Self-Improvement 101



Productivity 101

in
15 + 11 Mistakes Every Java Developer MUST avoid TODAY
Do you want to be your code reviewer’s favorite developer?
7 min read·May 1, 2024
314


in
It’s Time to Retire Terraform
Terraform exists in many people’s hearts much like a friend or a loved one, or maybe even an enemy. Whether it’s your job to maintain the…
10 min read·Apr 23, 2024
637


in
Google Lays Off Flutter and Dart Teams Following Python Team Cuts
The Truth Behind Google’s Mass Layoffs: How Tech Giants Adapt in the Era of AI
·4 min read·May 1, 2024
409


Github Copilot vs Amazon CodeWhisperer
Both Github Copilot and Amazon Code Whisperer are AI coding assistant tool i.e., an AI-powered tool that has been designed to help us…
5 min read·Jan 18, 2024
16