DEV Community

Cover image for JBang, the missing scripting tool of the Java ecosystem

JBang, the missing scripting tool of the Java ecosystem

The Java ecosystem has already two powerful project management tools, namely Maven and Gradle, yet it lacked a simple and powerful scripting tool.
This is where JBang comes in.
It is a minimalist but powerful Java, Kotlin and Groovy file launcher.
In fact, it allows you to run code as easily as you would run a script.
It also provides many other features such as dependency management, templates, and an App Store.
Let's explore JBang and its features in this post.

Setup

The jbang command-line can be installed on Windows, Linux, and macOS using different methods which are well documented here.
We can verify the installation by running jbang --version.

In addition to that, it's preferable to install the accompanying IDE extension for our favorite IDE.
The supported IDE extensions are listed here.

JBang does not depend on a JDK to JRE but a JDK is required to run scripts that use Java.
You can install one with JBang by running jbang jdk install 23 which will install the JDK 23.

We are now ready to write our first scripts.

First script

Let's create a simple script that prints "Hello, World!" to the console.

> jbang init helloworld.java
Enter fullscreen mode Exit fullscreen mode

This will create a file named helloworld.java that can be run with jbang helloworld.java.

> jbang helloworld.java
Hello world
Enter fullscreen mode Exit fullscreen mode

When you open the file, you will see that it is a plain Java file with a main method and a particular first line.

///usr/bin/env jbang "$0" "$@" ; exit $?
import static java.lang.System.*;

public class helloworld {
    public static void main(String... args) {
        out.println("Hello world");
    }
}
Enter fullscreen mode Exit fullscreen mode

As we will see, JBang script are made up of three parts: the shebang, optional properties and the script itself.
We'll use some properties the second part in the next sections but let's focus on the first part.

This part ///usr/bin/env jbang "$0" "$@" ; exit $? tells the system to use JBang to run the script.
It is called a shebang in the Unix ecosystem and is used to specify the interpreter for the script.
We can illustrate this on a Unix System (macOS, Linux) by running chmod +x helloworld.java to make the script executable and then running ./helloworld.java.

> chmod +x helloworld.java
> ./helloworld.java
Hello world
Enter fullscreen mode Exit fullscreen mode

We can now develop our script as we would with any Java file.
Once it is ready to be published, we can export it in different formats as follows:

  • A jar file: jbang export portable helloworld.java. If your script uses dependencies, the next commands are more recommended.
  • A fatjar: which contains all the dependencies: jbang export fatjar helloworld.java. This method still requires to install a JDK / JRE on the target machine. If you don't want to that, the next commands are more recommended.
  • A jlink binary that encompases a JDK: jbang export jlink helloworld.java. The binary to run is either helloworld-jlink/bin/helloworld on Unix or helloworld-jlink/bin/helloworld.bat on Windows.
  • A native imgae: jbang export native helloworld.java. This requires a GraalVM installation.

The script can also be exported as a mavenrepo with: jbang export mavenrepo helloworld.java

JDK management

As seen in a previous chapter, JBang can install JDKs on your machine.
You can list the installed JDKs with jbang jdk list, list available ones to install with jbang jdk list --available --show-details, install a new one with jbang jdk install [version]. Jbang also supports the use of SDKMAN to manage JDKs on supported systems.

Furthermore, it is possible to specify a JDK version in a scripts.
This is done by adding the following line to the script properties: //JAVA [version] if we want an exact version or //JAVA [version]+ if we want at least a specific version.
In that case JBang will automatically install the required JDK version and use it only for that script without changing the default JDK of the system.

For example, the following script uses Java 25 and some preview features.

/// usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 25
//COMPILE_OPTIONS --enable-preview -source 25
//RUNTIME_OPTIONS --enable-preview

import java.util.concurrent.Callable;
import java.util.concurrent.StructuredTaskScope;

import static java.lang.System.*;

void main(String... args) {
  out.println("Hello Java 25");
  Callable<String> task1 = () -> {
    out.println("Task 1" + Thread.currentThread());
    return "Task 1";
  };

  Callable<Integer> task2 = () -> {
    out.println("Task 2" + Thread.currentThread());
    return 2;
  };

  try (
    var scope = new StructuredTaskScope<Object>()) {
    StructuredTaskScope.Subtask<String> subtask1 = scope.fork(task1);
    StructuredTaskScope.Subtask<Integer> subtask2 = scope.fork(task2);
    scope.join();
  } catch (Exception e) {
    e.printStackTrace();
  }
}
Enter fullscreen mode Exit fullscreen mode

Scripts without a "Main" class

Since script tend to be lightweight, it would be preferable to write them without a class and a main method.
Fortunately, Java has a feature called implicit declared classes and instance main methods (which is still in preview in Java 23).
This feature allows to write java programs and JBang script without a class and a static main method.

The following script will be compiled and executed without any problem.

///usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 23+
//COMPILE_OPTIONS --enable-preview -source 23
//RUNTIME_OPTIONS --enable-preview

void main(String... args) {
  System.out.println("Hello World");
}
Enter fullscreen mode Exit fullscreen mode

This is made possible by adding the following properties to the script.

//JAVA 23+
//COMPILE_OPTIONS --enable-preview -source 23
//RUNTIME_OPTIONS --enable-preview
Enter fullscreen mode Exit fullscreen mode

The first line, //JAVA 23+, tells JBang to use Java 23 or later.
The second and third lines, //COMPILE_OPTIONS --enable-preview -source 23 and //RUNTIME_OPTIONS --enable-preview, enable preview features for compilation and runtime, respectively.

Once the feature become stable, we can remove the 3 lines and the script will still work. Neat!

Dependencies

JBang supports adding dependencies to scripts in the form of Gradle style dependencies by adding a //DEPS atrefact-id:atrefact-name:version line for each dependency.
For example, to use the jfiglet library, we can add the following line to the script: //DEPS com.github.lalyos:jfiglet:0.0.8.

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.github.lalyos:jfiglet:0.0.9

import com.github.lalyos.jfiglet.FigletFont;

public class DependenciesDemo {
    public static void main(String... args) throws Exception {
        System.out.println(FigletFont.convertOneLine("JBang is amazing"));
    }
}
Enter fullscreen mode Exit fullscreen mode

Catalogs

Catalogs in JBang allow to organize and share scripts and templates efficiently.
This feature is particularly useful for teams or communities that want to share a collection of scripts for common tasks or workflows.
It is also useful for teachers who want to distribute starter codes or show results of exercises without providing the source code.

A catalog is a JSON file named jbang-catalog.json that contains two groups of items: aliases and templates.
Aliases allow to run scripts from a catalog using a simple command, while templates provide a starting point for new scripts.
Catalogs can be remote or local and it is possible to add and use as many local or remote repositories as needed.
It is interesting to point out that JBang creates, during its setup, a local catalog with some aliases and templates out of the box.

JBang looks for local catalogs in these directories in the following order (source JBang docs):

  1. Current directory, ./jbang-catalog.json
  2. In ./.jbang/jbang-catalog.json
  3. In the parent directory, ../jbang-catalog.json
  4. In the parent’s .jbang directory, ../.jbang/jbang-catalog.json
  5. And repeating steps 3 and 4 recursively upwards to the root of the file system
  6. As the last step it will look in $HOME/.jbang/jbang-catalog.json

JBang will look for remote the catalogs in many open source repositories like GitHub, GitLab, Bitbucket, and others.
I'll use GitHub as an example in this post.
To create a remote catalog, you need to add jbang-catalog.json to the root folder of your repository.
The catalog is then referred to by account/repository_name.
If your repository is named jbang-catalog, then you can refer to it by account.
So, for example, if my GitHub account is named yostane and I have a repository named cours-java that contains a catalog which a file named jbang-catalog.json, I can refer to that catalog by yostane/cours-java. Furthermore, if I have a jbang-catalog.json in a repository named jbang-catalog then I can refer to it by yostane/jbang-catalog or simply yostane.

  {
    "catalogs": {},
    "aliases": {
      // aliases
    },
    "templates": {
      // templates
    }
  }
Enter fullscreen mode Exit fullscreen mode

The following chapters will show how to use aliases and templates from a catalog.

Aliases

Aliases in JBang allow to run scripts from a catalog.
The full syntax is jbang alias@account/repository [args] and jbang alias [args] for respectively a remote and local alias.

Aliases can be defined in the aliases section of the catalog file using the following format:

{
  "aliases": [
    {
      "name": "alias-name",
      "description": "Some description",
      "location": "path-to-script.java or .kt or .groovy"
    },
    // More aliases ...
  ]
}
Enter fullscreen mode Exit fullscreen mode

Here is the catalog I used during my session at DevoxxMA 2024.

{
    "catalogs": {},
    "aliases": {
      "palcli": {
        "script-ref": "tutorials/devoxxma2024-jbang/paltools/palcli.java",
        "description": "Palindrome tester CLI"
      },
    "palqrest": {
        "script-ref": "tutorials/devoxxma2024-jbang/paltools/palqrest.java",
        "description": "Palindrome tester Quarkus Rest API"
      },
    "hellojfx": {
        "script-ref": "tutorials/devoxxma2024-jbang/javafx/hellojfx.java",
        "description": "Basic JavaFX window that shows Java and JavaFx versions"
      }
    }
}
Enter fullscreen mode Exit fullscreen mode

You can run these aliases with the following commands:

  • jbang palcli@yostane/cours-java madam
  • jbang palqrest@yostane/cours-java
  • jbang hellojfx@yostane/cours-java

The official JBang GitHub account provides a catalog with many aliases and templates.
Let's run some of them:

  • jbang httpd@jbangdev run a local webserver.
  • jbang gavsearch@jbangdev [arg] search for [arg] on search.maven.org.

Templates

Templates, which are pre-defined scripts that can be used as a starting point for new scripts.
They are defined in the templates section of the catalog file using the following format:

{
  "catalogs": {},
  "aliases": {
    //...
  },
  "templates": {
    "template-name": {
      "file-refs": {
        "filename.java": "path-to-template-file.java, .kt, .groovy or .qute"
      },
      "description": "Some description"
    },
    // More templates ...
  }
}
Enter fullscreen mode Exit fullscreen mode

When a template is used, JBang creates copies of all the files int the file-refs property.
When a file-ref contains {basename}, JBang replaces it with the name of the script being created.
When a file-ref uses the .qute extension, JBang uses the Qute templating engine

Here are some examples of some templates available out of the box:

  • A CLI script that uses picocli: jbang init -t cli hellocli.java
  • A Quarkus single file REST API: jbang init -t qrest helloqrest.java

We can also use templates from the internet shared by the community.
For example, this command creates a JUnit unit test file: jbang init -t junit@jbangdev file_to_test.java.
From the command we can locate the jbang-catalog.json that defined the tempalte in the jbangdev/jbang-catalog repository.

{
  "catalogs": {},
  "aliases": {
    //...
  },
  "templates": {
    //...
    "junit": {
      "file-refs": {
        "{basename}Test.java": "templates/junit.java.qute"
      },
      "description": "Basic template for JUnit tests"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

App Store

The JBang App Store is a web app that allows to browse aliases of indexed catalogs.
It provides a convenient way to discover and use various tools and utilities without the need for complex setup or installation processes.
For example, when we search for yostane, we should be able to find the different aliases that I have defined in my different catalogs.
The following image shows the result of the search.

Image description

Here are some interesting and funny scripts that I found by browsing on the the App Store:

  • Cowsay. Here are some examples of running the script:
    • jbang cowsay@ricksbrown/cowsay MOO!
    • jbang cowsay@ricksbrown/cowsay -f dragon "I'm Veldora Tempest!"
  • Find substring like grep: jbang grep@a-services "hello" .
  • Create PDF from images: images2pdf@a-services. The following commands will create a PDF file from two images:
  wget -O img1.png "https://blog.worldline.tech/images/post/modulith/modulith-thumb.png"
  wget -O img2.png "https://blog.worldline.tech/images/post/ryuk/testcontainers-396x120.png"
  # Create a file that contains the paths to the images. This file is required by the script.
  echo "img1.png" > ./files.txt
  echo "img2.png" >> ./files.txt
  jbang images2pdf@a-services files.txt
Enter fullscreen mode Exit fullscreen mode

When you publish a catalog, it will very probably appear after the next indexing of the JBang AppStore.
It is a scheduled GitHub action defined here.

Some examples with notable frameowrks

With JBang, we can create single file applications that use popular frameworks and libraries.
Some examples include Quarkus, picolcli, and JavaFX.
Let's explore some example in the following sections.

JavaFX (openjfx)

JavaFX is a desktop and UI framework.
Its official website is openjfx.io and is also supported by Gluon which provides additional UI components and brings mobile app support to JavaFX.
JBang supports this frameowrk and can be used to create signle file JavaFX applications.

Here are some examples JavaFX apps created with JBang:

Quarkus

Quarkus is a Java framework that is optimized for Kubernetes and serverless environments.
It provides a fast boot time and low memory consumption, making it ideal for cloud-native applications.

Thanks to JBang, we can create single-file Quarkus applications that leverage the power of this framework.
The following example shows a rest API that tests if a string is a palindrome. It has JSON parsing, logging and provides an OpenAPI and Swagger documentation.

///usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 17+
// Update the Quarkus version to what you want here or run jbang with
// `-Dquarkus.version=<version>` to override it.
//DEPS io.quarkus:quarkus-bom:${quarkus.version:3.15.1}@pom
//DEPS io.quarkus:quarkus-resteasy
//DEPS io.quarkus:quarkus-resteasy-jsonb
//DEPS io.quarkus:quarkus-smallrye-openapi
//DEPS io.quarkus:quarkus-swagger-ui

//JAVAC_OPTIONS -parameters
//JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManager

//SOURCES PalindromeService.java

//Q:CONFIG quarkus.banner.enabled=false
//Q:CONFIG quarkus.swagger-ui.always-include=true

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;

import java.util.Map;

@Path("/palindrome")
@ApplicationScoped
public class palqrest {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, String> isPalidrome(@QueryParam("input") String input) {
        return Map.of("result",
                PalindromeService.isPalindrome(input) ? "Palindrome" : "Not Palindrome");
    }
}
Enter fullscreen mode Exit fullscreen mode

We may notice a //SOURCES PalindromeService.java line in the script.
It tells JBang to look for a file named PalindromeService.java in the same directory as the script.
This means that JBang supports multi-file scripts.

You can run the server with jbang palqrest@yostane/cours-java and call the endpoint with curl http://localhost:8080/palindrome?input=madam.

jbang palqrest@yostane/cours-java
# In another terminal
curl http://localhost:8080/palindrome?input=madam
# {"result":"Palindrome"}
Enter fullscreen mode Exit fullscreen mode

Other languages

JBang supports running Java, Kotlin, JShell and Groovy code.
It can even run Java code from markdown files.
Here are some examples of how to use JBang with different languages:

  • Kotlin: You can initialize a Kotlin script with jbang init -t hello.kt filename.kt. Please note that this is different from the official .main.kts Kotlin scripts. In fact, Kotlin scripts created by JBang can benefit from the catalog and App Store features. Here is an example of a Kotlin script created with JBang.
  ///usr/bin/env jbang "$0" "$@" ; exit $?
  //DEPS org.fusesource.jansi:jansi:2.4.1

  import org.fusesource.jansi.Ansi.*
  import org.fusesource.jansi.Ansi.Color.*
  import org.fusesource.jansi.AnsiConsole

  public fun main() {
      AnsiConsole.systemInstall()
      println(
          ansi().eraseScreen().fg(RED).a("Hello").fg(GREEN).a(" DevoxxMA ").fg(RED).a("2024").reset()
      )
      println(ansi().eraseScreen().render("@|red JBang|@ @|green 💖 Kotlin|@"))
  }
Enter fullscreen mode Exit fullscreen mode
  • Interesting fact: the idea of JBang came from kscript which is targeted to the Kotlin ecosystem.
  • Kotlin already has native scripting support (with .main.kts scripts) but seems to lack the catalog, template, and App Store features.
    • Groovy: Initialize a Groovy script with jbang init -t hello.groovy filename.groovy. Here is an example of a Groovy script created with JBang.
  ///usr/bin/env jbang "$0" "$@" ; exit $?
  println("Hello Groovy World");
Enter fullscreen mode Exit fullscreen mode
  • JShell: JBang supports JShell scripts with .jsh or .jshell extensions and inline scripts using jbang -c 'System.out.println("Inline Java ☕ yay!")'. Here is an example of a JShell script created with JBang.
  ///usr/bin/env jbang "$0" "$@" ; exit $?
  //DEPS com.github.lalyos:jfiglet:0.0.9

  import com.github.lalyos.jfiglet.FigletFont;

  System.out.println("hello from jshell");
  System.out.println(FigletFont.convertOneLine("DevoxxMA"));
Enter fullscreen mode Exit fullscreen mode
  • Markdown with Java and JShell code blocks: You can run Java and JShell code blocks directly from a markdown file using jbang my_markdown.md.
  # jbang runs markdown

  Jbang can execute code blocks.

  ```

java
  class Demo {
      void test() {
          System.out.println("I am a print in a markdown!");
      }
  }


  ```

  ```

jshelllanguage
  new Demo().test();


  ```

  ```

jsh
  //DEPS com.github.lalyos:jfiglet:0.0.9
  import com.github.lalyos.jfiglet.FigletFont;

  System.out.println(FigletFont.convertOneLine(
        "Hello " + ((args.length>0)?args[0]:"jbang")));


  ```

  ```

java
  if(args.length==0){
      System.out.

  println("You have no arguments!");
  }else{
      System.out.

  printf("You have %s arguments! First is %s",args.length, args[0]);
  }


  ```

  ```

kt
  fun main() {
      print("We love Kotlin")
  }


  ```

  ```

kotlin
  println("We love Kotlin")


  ```

  ```

groovy
  println("We love Kotlin")


  ```
  ```

`

## Other and experimental features

In this section, we will delve into some additional and experimental features of JBang that can further enhance your scripting experience.
These features include file configuration, caching, the JBang wrapper, and even generating scripts using OpenAI.
Let's briefly view some of them next.

### File Configuration

JBang allows you to configure various settings through a configuration file.
This can be particularly useful for setting default options, managing dependencies, and customizing the behavior of your scripts.
You can learn more about file configuration [here](https://www.jbang.dev/documentation/guide/latest/config.html).

### Caching

To improve performance and reduce redundant downloads, JBang supports caching of dependencies and other resources.
For certain situations, we may want to clear the cache with `jbang cache clear` and ignore the cache when fetching a catalog with `--fresh` argument.
For example, `jbang --fresh palqrest@yostane/cours-java` will ignore the cache and get the latest version of the alias.
Documentation on caching can be found [here](https://www.jbang.dev/documentation/guide/latest/caching.html).

### JBang Wrapper

The JBang wrapper is a convenient tool that allows you to bundle JBang with your project.
This ensures that anyone who clones your repository can run your scripts without needing to install JBang separately.
More details on the JBang wrapper are available [here](https://www.jbang.dev/documentation/guide/latest/cli/jbang-wrapper.html).

### OpenAI Script Generation (Experimental)

JBang has the ability to generate scripts using OpenAI.
By providing a description of what you want your script to do, JBang can leverage OpenAI's capabilities to create a script for you.
This feature is still in preview and requires an OpenAI API key (I personally could not get an OpenAPI key so I couldn't test by myself).

For example, you can generate a script to count the number of people in an image using OpenCV with the following command:

```sh
jbang --preview --verbose init ImageProcessor.java "Write a Java program that counts the number of people in an image with OpenCV"
```

As a side note, if we want to prompt from the terminal, there are already many stable tools such as [mods](https://github.com/charmbracelet/mods).

## Conclusion

JBang is a highly versatile tool that brings powerful scripting capabilities to the Java ecosystem.
It is particularly useful for single-file projects, allowing developers to quickly and easily run Java, Kotlin, and Groovy scripts.
It can be applied to a wide range of use cases: Rest APIs, GUI apps, general scripting, etc.
The tool's features, such as templating, the App Store, and catalogs, enhance its utility by facilitating sharing and collaboration among developers.

Whether it is for teaching or for scripting, JBang is a valuable addition to the Java developer's toolkit.

## Credits and resources

- [jbang](https://jbang.dev)
- [jfiglet](https://github.com/lalyos/jfiglet)
- [fusesource/jansi](https://github.com/fusesource/jansi)
- [Java 23 Implicitly Declared Classes and Instance Main Methods](https://docs.oracle.com/en/java/javase/23/language/implicitly-declared-classes-and-instance-main-methods.html)
- [baeldung.com/jbang](https://www.baeldung.com/jbang-guide)
- [JBang OpenAI example](https://www.infoq.com/news/2023/06/jbang-107/)
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
djeang profile image
Jerome Angibaud

Nice tool indeed.
There is also jeka.dev
It does similar things and more.