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 "$0" "$@" ; 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
.
> chmod +x helloworld.java
> ./helloworld.java
Hello world
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 eitherhelloworld-jlink/bin/helloworld
on Unix orhelloworld-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();
}
}
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");
}
This is made possible by adding the following properties to the script.
//JAVA 23+
//COMPILE_OPTIONS --enable-preview -source 23
//RUNTIME_OPTIONS --enable-preview
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"));
}
}
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):
- Current directory,
./jbang-catalog.json
- In
./.jbang/jbang-catalog.json
- In the parent directory,
../jbang-catalog.json
- In the parent’s .jbang directory,
../.jbang/jbang-catalog.json
- And repeating steps 3 and 4 recursively upwards to the root of the file system
- 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
}
}
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 ...
]
}
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"
}
}
}
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]
onsearch.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 ...
}
}
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"
}
}
}
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.
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
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.
///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");
}
}
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"}
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|@"))
}
- 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.
-
Groovy: Initialize a Groovy script with
///usr/bin/env jbang "$0" "$@" ; exit $?
println("Hello Groovy World");
-
JShell: JBang supports JShell scripts with
.jsh
or.jshell
extensions and inline scripts usingjbang -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"));
-
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/)
Top comments (1)
Nice tool indeed.
There is also jeka.dev
It does similar things and more.