Home >Java >javaTutorial >JBang, the missing scripting tool of the Java ecosystem

JBang, the missing scripting tool of the Java ecosystem

Patricia Arquette
Patricia ArquetteOriginal
2025-01-05 04:01:39196browse

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

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

> jbang helloworld.java
Hello world

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 "<pre class="brush:php;toolbar:false">> chmod +x helloworld.java
> ./helloworld.java
Hello world
" "$@" ; exit $? import static java.lang.System.*; public class helloworld { public static void main(String... args) { out.println("Hello world"); } }

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.

/// usr/bin/env jbang "<pre class="brush:php;toolbar:false">///usr/bin/env jbang "<pre class="brush:php;toolbar:false">//JAVA 23+
//COMPILE_OPTIONS --enable-preview -source 23
//RUNTIME_OPTIONS --enable-preview
" "$@" ; exit $? //JAVA 23+ //COMPILE_OPTIONS --enable-preview -source 23 //RUNTIME_OPTIONS --enable-preview void main(String... args) { System.out.println("Hello World"); } " "$@" ; 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 task1 = () -> { out.println("Task 1" + Thread.currentThread()); return "Task 1"; }; Callable task2 = () -> { out.println("Task 2" + Thread.currentThread()); return 2; }; try ( var scope = new StructuredTaskScope()) { StructuredTaskScope.Subtask subtask1 = scope.fork(task1); StructuredTaskScope.Subtask subtask2 = scope.fork(task2); scope.join(); } catch (Exception e) { e.printStackTrace(); } }

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.

> jbang init helloworld.java

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.

> jbang helloworld.java
Hello world

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

///usr/bin/env jbang "<pre class="brush:php;toolbar:false">> chmod +x helloworld.java
> ./helloworld.java
Hello world
" "$@" ; exit $? import static java.lang.System.*; public class helloworld { public static void main(String... args) { out.println("Hello world"); } }

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.

> jbang init helloworld.java

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.

> jbang helloworld.java
Hello world

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:

> jbang init helloworld.java

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

> jbang helloworld.java
Hello world

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:

///usr/bin/env jbang "<pre class="brush:php;toolbar:false">> chmod +x helloworld.java
> ./helloworld.java
Hello world
" "$@" ; exit $? import static java.lang.System.*; public class helloworld { public static void main(String... args) { out.println("Hello world"); } }

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.

/// usr/bin/env jbang "<pre class="brush:php;toolbar:false">///usr/bin/env jbang "<pre class="brush:php;toolbar:false">//JAVA 23+
//COMPILE_OPTIONS --enable-preview -source 23
//RUNTIME_OPTIONS --enable-preview
" "$@" ; exit $? //JAVA 23+ //COMPILE_OPTIONS --enable-preview -source 23 //RUNTIME_OPTIONS --enable-preview void main(String... args) { System.out.println("Hello World"); } " "$@" ; 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 task1 = () -> { out.println("Task 1" + Thread.currentThread()); return "Task 1"; }; Callable task2 = () -> { out.println("Task 2" + Thread.currentThread()); return 2; }; try ( var scope = new StructuredTaskScope()) { StructuredTaskScope.Subtask subtask1 = scope.fork(task1); StructuredTaskScope.Subtask subtask2 = scope.fork(task2); scope.join(); } catch (Exception e) { e.printStackTrace(); } }

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.

JBang, the missing scripting tool of the Java ecosystem

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:
///usr/bin/env jbang "<pre class="brush:php;toolbar:false">  {
    "catalogs": {},
    "aliases": {
      // aliases
    },
    "templates": {
      // templates
    }
  }
" "$@" ; 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")); } }

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:

  • Basic window
  • More beautiful example jbang https://gist.github.com/FDelporte/c69a02c57acc892b4c996a9779d4f830
  • A template jbang init -t javafx@yostane hellojfx

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.

> jbang init helloworld.java

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 helloworld.java
Hello world

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.
> jbang init helloworld.java
  • 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.
> jbang helloworld.java
Hello world
  • 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 "<pre class="brush:php;toolbar:false">> chmod +x helloworld.java
> ./helloworld.java
Hello world
" "$@" ; exit $? import static java.lang.System.*; public class helloworld { public static void main(String... args) { out.println("Hello world"); } }
  • 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.
/// usr/bin/env jbang "<pre class="brush:php;toolbar:false">///usr/bin/env jbang "<pre class="brush:php;toolbar:false">//JAVA 23+
//COMPILE_OPTIONS --enable-preview -source 23
//RUNTIME_OPTIONS --enable-preview
" "$@" ; exit $? //JAVA 23+ //COMPILE_OPTIONS --enable-preview -source 23 //RUNTIME_OPTIONS --enable-preview void main(String... args) { System.out.println("Hello World"); } " "$@" ; 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 task1 = () -> { out.println("Task 1" + Thread.currentThread()); return "Task 1"; }; Callable task2 = () -> { out.println("Task 2" + Thread.currentThread()); return 2; }; try ( var scope = new StructuredTaskScope()) { StructuredTaskScope.Subtask subtask1 = scope.fork(task1); StructuredTaskScope.Subtask subtask2 = scope.fork(task2); scope.join(); } catch (Exception e) { e.printStackTrace(); } }

The above is the detailed content of JBang, the missing scripting tool of the Java ecosystem. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn