When developing on the JVM, we may write single file programs. In this situation, we want to build and run them in a very simple way. Classical tools such as Gradle or Maven are inconvenient for such cases. That’s what JBang addresses in addition to bringing nice exclusive features. Let’s explore them in this post.
Quick start
JBang CLI (Command Line Interface) can be installed in many ways. The most straightforward one is to run one of these scripts depending on your OS:
#Linux/OSX/Windows/AIX Bash
curl -Ls https://sh.jbang.dev | bash -s - app setup
#Windows Powershell
iex "& { $(iwr -useb https://ps.jbang.dev) } app setup"
Once the setup is complete, we can open a new terminal session and start using the CLI. We can create a java file named “helloworld.java” and run it as follows:
jbang init helloworld.java
jbang helloworld.java # The file will be executed and will print "Hello world"
The generated Java file is a typical one except for the first line, which marks the file as a JBang script.
///usr/bin/env jbang "$0" "$@" ; exit $?
import static java.lang.System.*;
public class helloworld {
public static void main(String... args) {
out.println("Hello world");
}
}
JBang also supports running Kotlin and Groovy files. Here is an example of creating and running a Kotlin file:
jbang init -t hello.kt helloworld.kt
jbang helloworld.kt #will run the file and print "hello world"
This are some basic features of JBang that unlock many use cases such as teaching, running custom CI scripts and prototyping.
Let’s see some more interesting single file apps that we can create with JBang
Using Jbang with libraries
JBang files can be configured by adding special comments before the comments and any code. Here are some examples:
| Description | Syntax |
| Add a dependency | //DEPS “gradle style dependency” |
| Add a file | //SOURCES “relative path to source file” |
| Use experimental features of Java 23 | //JAVA 23+ //COMPILE_OPTIONS –enable-preview -source 23 //RUNTIME_OPTIONS –enable-preview |
| Quarkus property | //Q:CONFIG “property”=”value” |
Let’s illustrate next with some concrete examples.
Java File without a main class
This is an experimental feature of Java 23. Thus, we need to use the options that enforce Java 23 as well as the compiler and runtime options that activate experimental features.
///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");
}
Quarkus Rest API with JSON parsing
JBang supports the BOM feature, allowing to define the version of Quarkus in one line and apply it to all its extensions without having to repeat the version number.
The following example uses quarkus-resteasy-jsonb for JSON parsing because it does not require to setup compiler or build plugins, making it simpler and more relevant for our use case.
///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");
}
}
The file PalindromeService.java imported by the script is defined as follows:
public class PalindromeService {
static boolean isPalindrome(String input) {
int l = input.length();
for (int i = 0; i < l / 2; i++) {
if (input.charAt(i) != input.charAt(l - i - 1)) {
return false;
}
}
return true;
}
}
Isolating the service in a separate file makes it easier to develop a CLI variation with picocli:
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.6.3
//SOURCES PalindromeService.java
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import java.util.concurrent.Callable;
@Command(name = "palcli", mixinStandardHelpOptions = true, version = "palcli 0.1", description = "palcli made with jbang")
class palcli implements Callable<Integer> {
@Parameters(index = "0", description = "The greeting to print", defaultValue = "World!")
private String inputString;
public static void main(String... args) {
int exitCode = new CommandLine(new palcli()).execute(args);
System.exit(exitCode);
}
@Override
public Integer call() throws Exception { // your business logic goes here...
System.out.println(PalindromeService.isPalindrome(inputString) ? "Palindrome" : "Not a Palindrome");
return 0;
}
}
Sharing with catalogs
JBang catalogs provide an easy way to run scripts, also called aliases, and import templates from external sources. Defining a catalog consists of adding a JSON file named ‘jbang-catalog.json’ in the root folder of any GitHub repository. For example, I host a catalog in github.com/yostane/jbang-catalog/jbang-catalog.json with the following content:
{
"catalogs": {},
"aliases": {
"palcli": {
"script-ref": "scripts/paltools/palcli.java",
"description": "Palindrome tester CLI"
},
"hellojfx": {
"script-ref": "scripts/hellojfx.java",
"description": "Basic JavaFX window that shows Java and JavaFx versions"
}
},
"templates": {
"javafx": {
"file-refs": {
"{basename}.java": "templates/javafx.java.qute"
},
"description": "A starter JavaFX app"
}
}
}
My catalog defines two aliases and a template. The basic syntax of calling an alias is “jbang alias@github_user/repository [args]”. The default value of repository is “jbang-catalog”. Thus, we can run the two alises as follows:
jbang palcli@yostane madam #will check if madam is a palindrome
jbang hellojfx@yostane #Shows a JavaFX window
We can browse through aliases provided by the community in the JBang AppStore which is a webpage that indexes all JBang catalogs on GitHub.
Coming back to my catalog, we note that is contains a template in addition to the aliases. This template generates a JavaFX application and can be used with this command:
jbang init -t javafx@yostane hellojfx #generates a java file named hellojfx.java
jbang hellojfx.java #run the file generated by the template
For more details on how to setup a catalog, please refer to the documentation.
Why Jbang
After exposing the most important features of JBang, we may be challenged about the relevance of this tool compared to what is currently available. Even though technical choices are somewhat influenced by comfort and other subjective preferences, let me try to address some questions in an objective way:
Compared to Maven and Gradle
Let’s suppose you want to develop prototypes that fit in one or two Java files or you are a teacher who wants to showcase Java (or Kotlin or Groovy) features in small apps. If we use Maven or Gradle, Imagine how many projects, folders and files would have been created. With JBang, since each Java file is autonomous (it contains the code and the settings), then a single folder that contains only Java file is enough.
In summary, while Maven and Gradle are awesome for medium and large projects, JBang is more relevant for small apps and for teaching. In addition to that, teachers can take advantage of catalogs to share templates and aliases to their students.
Compared to an interpreted language
This is a relevant question: why use a compiled language for scripting purposes instead of using an interpreted language such as Bash, JS or Python?
In my opinion, it depends on what we mean by scripting. If we define it as using an interpreted language, than there is no discussion by definition since Java is compiled. Although, we can argue that Java is compiled and interpreted and some scripting languages do some kind of compilation.
Personally, I prefer to define scripting as writing small apps in a short period of time that accomplish very specific tasks. This definition is bound to the goal and not the technology. This means that whatever tool fits the criteria can be used. In that case, teams consisting of Java of Kotlin devs can be pragmatic and use JBang instead of learning Bash, JS or any other scripting language.
Conclusion
While being initially loved for it’s simplicity, some qualify Java of being heavy and complex. Maybe part of this is related to the structure of Maven and Gradle projects. JBang brings back the simplicity to Java projects while providing powerful and modern features such as catalogs and using dependencies. It makes writing small apps a breeze and teaching Java even more joyful.