Femtocli: A small but mighty CLI library for small CLI tools in < 45KB

TL;DR: I built femtocli, a small command-line parsing library with sub-commands, an annotation-based API, and support for parsing Java agent arguments.

Every command-line tool that I, and probably you, write needs a command-line interface (CLI). Writing simply, with one or two options manually, is possible, but becomes tedious fast. Especially when our tool grows, and we add more options and subcommands.

Usually, you would add a library like the fantastic picocli to your project and be fine. It’s a really easy-to-use CLI library with all the features that you would want. So you can write a simple CLI quite quickly and declaratively:

@Command(name = "demo", description = "A demo application")
public class Main implements Runnable {

    @CommandLine.Parameters(index = "0", description = "Some parameter")
    String parameter;

    @Option(names = "--some-flag")
    boolean someFlag;

    @Override
    public void run() { /* print */ }

    public static void main(String[] args) {
        new CommandLine(new Main()).execute(args);
    }
}

The only problem? While the application JAR itself is only around 3KB for these tiny examples, the application JAR with the Picocli 4.7.7 dependency is 407KB.

Is this a problem? It might not be for you, but it certainly can be for small tools like jstall, where the Picocli dependency accounted for over half the JAR size in the minimal version, which is kind of a lot just for the option parsing. Of course, in many situations you don’t care, as there are other libraries, e.g., for JSON parsing, that can easily add multiple megabytes. But for tiny CLI helper tools, it can matter.

I couldn’t find any maintained CLI library with a picocli-like declarative approach and sub-commands under 50KB, so I built one. And the best part: I was able to add all the features I liked (converters, custom footers, …) and ignore the ones I don’t usually need (GraalVM support, ANSI colors, …).

The new library is called femtocli, it requires Java 17 and is MIT licensed:

package me.bechberger.femtocli.examples;

import me.bechberger.femtocli.FemtoCli;
import me.bechberger.femtocli.annotations.Command;
import me.bechberger.femtocli.annotations.Option;

import java.util.concurrent.Callable;

@Command(name = "greet", description = "Greet a person")
class GreetCommand implements Callable<Integer> {
    @Option(names = {"-n", "--name"}, description = "Name to greet", required = true)
    String name;

    @Option(names = {"-c", "--count"}, description = "Count (default: ${DEFAULT-VALUE})", defaultValue = "1")
    int count;

    @Override
    public Integer call() {
        for (int i = 0; i < count; i++) System.out.println("Hello, " + name + "!");
        return 0;
    }
}

@Command(name = "myapp", description = "My CLI application", version = "1.0.0",
        subcommands = {GreetCommand.class})
public class QuickStart implements Runnable {
    public void run() {
        System.out.println("Use 'myapp greet --help'");
    }

    public static void main(String[] args) {
        FemtoCli.run(new QuickStart(), args);
    }
}

Try it:

> ./examples/run.sh QuickStart greet --name=World --count=1
Hello, World!
Continue reading

The Java Version Quiz

Over the last 30 years, Java has added many features, including generics, lambdas, pattern matching, and records. You surely know that lambdas have been introduced in Java eight and records in Java 16, but can you distinguish the other Java versions?

I felt I couldn’t, so I created a tiny little Java game: The Java Version Quiz. In this quiz, you get a Java snippet and have to decide between five different Java versions. Pick the smallest Java version where the snippet is valid code (without using preview features).

The screenshot shows the alpha version of the game, which includes features introduced in Java 1.0alpha2 and 1.0alpha3, including bug fixes. A game version for only the nerdiest of Java connaisseurs. Source is a dump of the alpha2 and alpha3 packages on GitHub.

The game focuses on Java language differences and major runtime differences, which are easy to check without semantic analysis. It’s a by-product of another fun little project.

I hope you learn some new features of Java and discover that it is evolving over the years, while still keeping the syntax similar enough that it’s hard to spot the differences between versions. And if you’re unsure what a specific feature in the shown code snippet is, the quiz gives you a handy description.

If you find any issues or have new code examples, feel free to contribute to the quiz on GitHub.

See you in another week for another blog post on something JFR-related.

CAP in the pocket: Developing Java Applications on your Phone

Smartphones are more powerful then ever, with processors rivaling old laptops. So let’s try to use them like a laptop to develop web-applications on the go. In this weeks blog post I’ll show you how to do use run and develop a CAP Java Spring Boot application on your smartphone and how to run VSCode locally to develop and modify it. This, of course, works only on Android phones, as they are a Linux at their core.

Continue reading

Mapping Java Thread Ids to OS Thread Ids

This week, a short blog post on a question that bothered me this week: How can I get the operating systems thread ID for a given Java thread? This is useful when you want to deal with Java threads using native code (foreshadowing another blog post). The question was asked countless times on the internet, but I couldn’t find a comprehensive collection, so here’s my take. But first some background:

Background

In Java, normal threads are mapped 1:1 to operating system threads. This is not the case for virtual threads because they are multiplexed on fewer carrier threads than virtual threads, but we ignore these threads for simplicity here.

But what is an operating system thread? An operating system thread is an operating system task that shares the address space (and more) with other thread tasks of the same process/thread group. The main thread is the thread group leader; its operating system ID is the same as the process ID.

Be aware that the Java thread ID is not related to the operating system ID but rather to the Java thread creation order. Now, what different options do we have to translate between the two?

Different Options

During my research, I found three different mechanisms:

  1. Using the gettid() method
  2. Using JFR
  3. Parsing thread dumps

In the end, I found that option 3 is best; you’ll see why in the following.

Continue reading

Wait you can place Java annotations there?

I worked too much on other stuff, so I didn’t have time to blog, so here is a tiny post.

Java annotations are pretty nice: You can annotate many things to add more information. For example, you can add an @Nullable to a type used to tell static analyzers or IDEs that this the value of this type there might actually be null:

public @Nullable String parse(String description) {
  ...
  return error ? null : result;
}

There are many other uses, especially in adding more information needed for code generation. In working on hello-ebpf, I used annotations and generated code with JavaPoet containing annotations. When we generate the code from above with JavaPoet, it produces:

public java.lang. @Nullable String parse(
  java.lang.String description) {
  // ...
}

But how could this be valid Java? I expected

public @Nullable java.lang.String parse(
  java.lang.String description) {
  // ...
}

but not the former. Let’s look into the language specification. Section 4.3 tells us class types in fields and other type usages as follows:

ClassType:
  {Annotation} TypeIdentifier [TypeArguments]
  PackageName . {Annotation} TypeIdentifier [TypeArguments]
  ClassOrInterfaceType . {Annotation} TypeIdentifier [TypeArguments] 

According to the specification @Nullable java.lang.String and java.lang. @Nullable String are the same.

It gets even weirder with arrays:

java.lang. @Nullable Integer @Nullable [] arr @Nullable []

This denotes a two-dimensional array of strings that might be null and might contain null, and its arrays might contain null. This is true to the language specification:

ArrayType:
  PrimitiveType Dims
  ClassOrInterfaceType Dims
  TypeVariable Dims
Dims:
  {Annotation} [ ] {{Annotation} [ ]}

There is even an example in the specification that is similar to our example:

For example, given the field declaration:

@Foo int f;

@Foo is a declaration annotation on f if Foo is meta-annotated by @Target(ElementType.FIELD), and a type annotation on int if Foo is meta-annotated by @Target(ElementType.TYPE_USE). It is possible for @Foo to be both a declaration annotation and a type annotation simultaneously.

Type annotations can apply to an array type or any component type thereof (§10.1). For example, assuming that A, B, and C are annotation interfaces meta-annotated with @Target(ElementType.TYPE_USE), then given the field declaration:

@C int @A [] @B [] f;

@A applies to the array type int[][], @B applies to its component type int[], and @C applies to the element type int. For more examples, see §10.2.

An important property of this syntax is that, in two declarations that differ only in the number of array levels, the annotations to the left of the type refer to the same type. For example, @C applies to the type int in all of the following declarations:

@C int f;
@C int[] f;
@C int[][] f;
Language Specification Section 9.7.4

Conclusion

Java never stops surprising me. This syntax looked weird when I first stumbled upon it, but after looking through the language specification, I see how useful and justified this placement of annotations is.

I hope you enjoyed this tiny blog post on annotations; see you in my next one.

P.S.: I’m currently at KCDC