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.
https://engineering.peerislands.io/media/2ea925300d0c1df7833e83c7367957e7
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.
https://engineering.peerislands.io/media/92c411ba386795d1b4efc9b79fd36c5a
Add .gitignore file
Here is what I have used as a common template across projects.
https://engineering.peerislands.io/media/43fff9f05a9bedb062f71b0adbd6a715
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.
https://engineering.peerislands.io/media/209a08335888e60755608b93a683bb95
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.
https://engineering.peerislands.io/media/e51b053d35a41d95621041bf0faa1931
JWTCEncode and JWTCDecode are the sub-command implementations.
https://engineering.peerislands.io/media/5280e1992103fa123c1d63774cec5f2a
https://engineering.peerislands.io/media/156d6f72cf714dbfe5cc73419fada69d
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.
https://engineering.peerislands.io/media/0ed3988616bcc355a754cab321fe6d99
The following command converts the existing application into a native image.
https://engineering.peerislands.io/media/794cc95cfb6d5b0673cd1e19ec2895b8
https://engineering.peerislands.io/media/c0e2035f3a2d3d8c13de92ffb45d07cb
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.
https://engineering.peerislands.io/media/f48e6c75fdf1b5e216ae265f966b1676
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.
https://engineering.peerislands.io/media/591cdf7d4a36ede651158b3ac0a9ad70
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.
https://engineering.peerislands.io/media/a1e345089d3ad3f9ac4f266a6163f791
I then built operating system specific distributions using the following profile.
https://engineering.peerislands.io/media/d15b814a2e883b82ed8ab1f8c995d2f0
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.
https://engineering.peerislands.io/media/b318df43a9885d946a12eaa3af0a21a3
https://engineering.peerislands.io/media/5e79bd1660d967a1d34d16b38c61060c
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
https://engineering.peerislands.io/media/6b12c37a4dcafbdcd28ce5f4ae74f1e8
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.
https://engineering.peerislands.io/media/6d8e5b92f6a12236928bc160b1c51c3c
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.
https://engineering.peerislands.io/media/748718ef84f951600e63d17f0869229b
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