JBang: La Herramienta de Scripting que le Faltaba a Java

Rossana SuarezRossana Suarez
8 min read

El ecosistema Java ya cuenta con poderosas herramientas de gestión de proyectos como Maven y Gradle, pero hasta ahora carecía de una herramienta de scripting simple y potente. Aquí es donde entra JBang, un lanzador minimalista pero versátil para archivos Java, Kotlin y Groovy que está transformando la manera en que escribimos scripts en el ecosistema Java.

¿Qué es JBang?

JBang es una herramienta que permite ejecutar código Java (y otros lenguajes JVM) tan fácilmente como ejecutarías un script en Python o JavaScript. Lo más impresionante es que incorpora gestión de dependencias, plantillas y hasta tiene su propia "App Store".

💡
Documentación: https://www.jbang.dev/

Instalación y Configuración

La instalación de JBang es sencilla y está disponible para Windows, Linux y macOS. Aquí te muestro los pasos básicos:

En Windows

curl -Ls https://sh.jbang.dev | bash

En Linux/macOS

curl -Ls https://sh.jbang.dev | bash

Para verificar la instalación:

jbang --version

Instalación de JDK

JBang puede manejar la instalación de JDK por ti:

jbang jdk install 23

extension vsc

Tu Primer Script con JBang

Vamos a crear nuestro primer script. Es tan simple como:

jbang init helloworld.java

Esto creará un archivo con este contenido:

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

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

Para ejecutarlo:

jbang helloworld.java
# Salida: Hello world

Gestión de Dependencias

Una de las características más potentes de JBang es su manejo de dependencias. Veamos un ejemplo más elaborado usando una librería externa para crear texto ASCII art:

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

import com.github.lalyos.jfiglet.FigletFont;

public class AsciiArt {
    public static void main(String... args) throws Exception {
        String text = args.length > 0 ? args[0] : "JBang";
        System.out.println(FigletFont.convertOneLine(text));
    }
}

Al ejecutarlo:

jbang AsciiArt.java "Hola"

Obtendrás algo como:

  _    _       _       
 | |  | |     | |      
 | |__| | ___ | | __ _ 
 |  __  |/ _ \| |/ _` |
 | |  | | (_) | | (_| |
 |_|  |_|\___/|_|\__,_|

Scripts en Groovy

JBang también soporta Groovy, lo que nos permite escribir scripts más concisos. Aquí hay algunos ejemplos:

Ejemplo Básico en Groovy

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.apache.commons:commons-lang3:3.12.0

import org.apache.commons.lang3.StringUtils

def mensaje = "¡Hola desde Groovy!"
println StringUtils.reverse(mensaje)
println "Argumentos recibidos: ${args}"

Ejemplo más Complejo en Groovy con HTTP

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.apache.httpcomponents:httpclient:4.5.13
//DEPS com.fasterxml.jackson.core:jackson-databind:2.13.0

import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.HttpClients
import com.fasterxml.jackson.databind.ObjectMapper

def httpClient = HttpClients.createDefault()
def mapper = new ObjectMapper()

def response = httpClient.execute(new HttpGet("https://api.github.com/users/jbangdev"))
def json = mapper.readTree(response.entity.content)

println """
GitHub User Info:
----------------
Name: ${json.name}
Bio: ${json.bio}
Followers: ${json.followers}
Following: ${json.following}
"""

Aplicaciones REST con Quarkus

JBang hace que crear APIs REST sea sorprendentemente fácil. Aquí tienes un ejemplo completo:

///usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 17+
//DEPS io.quarkus:quarkus-bom:3.15.1@pom
//DEPS io.quarkus:quarkus-resteasy
//DEPS io.quarkus:quarkus-resteasy-jsonb
//DEPS io.quarkus:quarkus-swagger-ui

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Path("/tareas")
public class TareasAPI {
    private static final Map<Integer, String> tareas = new ConcurrentHashMap<>();
    private static int contador = 1;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Map<Integer, String> listarTareas() {
        return tareas;
    }

    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> agregarTarea(String tarea) {
        int id = contador++;
        tareas.put(id, tarea);
        return Map.of(
            "id", id,
            "tarea", tarea,
            "mensaje", "Tarea agregada exitosamente"
        );
    }
}

Para probar esta API:

# Inicia el servidor
jbang TareasAPI.java

# En otra terminal, prueba los endpoints
curl -X POST -H "Content-Type: text/plain" -d "Comprar víveres" http://localhost:8080/tareas
curl http://localhost:8080/tareas

Aplicaciones de Escritorio con JavaFX

JBang también facilita la creación de aplicaciones GUI. Aquí un ejemplo con JavaFX:

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.openjfx:javafx-controls:19
//DEPS org.openjfx:javafx-fxml:19

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class AppJavaFX extends Application {
    @Override
    public void start(Stage stage) {
        Button btn = new Button("¡Haz clic!");
        btn.setOnAction(e -> System.out.println("¡Botón presionado!"));

        VBox root = new VBox(btn);
        Scene scene = new Scene(root, 300, 200);

        stage.setTitle("Demo JavaFX con JBang");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

Catálogos y App Store

Uso de Catálogos

JBang permite crear y compartir catálogos de scripts. Aquí un ejemplo de jbang-catalog.json:

{
    "catalogs": {},
    "aliases": {
        "http-server": {
            "script-ref": "scripts/HttpServer.java",
            "description": "Servidor HTTP simple"
        },
        "backup": {
            "script-ref": "scripts/Backup.java",
            "description": "Script de respaldo"
        }
    }
}

Ejemplos de la App Store

Algunos scripts útiles disponibles en la App Store de JBang:

# Servidor HTTP simple
jbang httpd@jbangdev

# Búsqueda en Maven Central
jbang gavsearch@jbangdev spring-boot

# Mensaje divertido con una vaca
jbang cowsay@ricksbrown/cowsay "¡Moo desde JBang!"

Características Avanzadas

Scripts sin Clase Principal

A partir de Java 23, puedes escribir scripts más concisos:

///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("¡Hola desde un script moderno!");
}

Soporte para Markdown

JBang puede ejecutar código Java dentro de archivos Markdown:

# Mi Script en Markdown

Este es un documento Markdown con código ejecutable.

```java
System.out.println("¡Ejecutando desde Markdown!");

## Consejos y Mejores Prácticas

1. **Gestión de Versiones de JDK**
   ```bash
   # Listar JDKs instalados
   jbang jdk list

   # Ver JDKs disponibles
   jbang jdk list --available
  1. Exportación de Scripts

     # Crear un JAR ejecutable
     jbang export portable myscript.java
    
     # Crear una imagen nativa
     jbang export native myscript.java
    
  2. Caché y Rendimiento

     # Limpiar caché
     jbang cache clear
    
     # Ignorar caché al ejecutar
     jbang --fresh myscript.java
    

JBang para DevOps: Ejemplo Simple

Aquí tienes un ejemplo sencillo pero útil de un script JBang para tareas DevOps básicas:

HealthChecker.java

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.squareup.okhttp3:okhttp:4.12.0
//DEPS info.picocli:picocli:4.7.5

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.util.concurrent.Callable;

@Command(name = "healthcheck", 
        description = "Verifica el estado de servicios y genera un reporte simple",
        version = "healthcheck 1.0")
public class HealthChecker implements Callable<Integer> {

    @Option(names = {"-u", "--urls"}, description = "URLs a verificar", required = true, split = ",")
    private String[] urls;

    @Option(names = {"-t", "--timeout"}, description = "Timeout en segundos", defaultValue = "30")
    private int timeout;

    private static final OkHttpClient client = new OkHttpClient();

    @Override
    public Integer call() {
        System.out.println("🔍 Iniciando verificación de servicios...\n");

        boolean allOk = true;

        for (String url : urls) {
            try {
                Request request = new Request.Builder()
                    .url(url)
                    .build();

                try (Response response = client.newCall(request).execute()) {
                    boolean isHealthy = response.isSuccessful();
                    String status = isHealthy ? "✅ ACTIVO" : "❌ CAÍDO";

                    System.out.printf("Servicio: %s\n", url);
                    System.out.printf("Estado: %s\n", status);
                    System.out.printf("Código: %d\n", response.code());
                    System.out.println("------------------------\n");

                    if (!isHealthy) {
                        allOk = false;
                    }
                }
            } catch (Exception e) {
                System.out.printf("Servicio: %s\n", url);
                System.out.printf("Estado: ❌ ERROR\n");
                System.out.printf("Error: %s\n", e.getMessage());
                System.out.println("------------------------\n");
                allOk = false;
            }
        }

        if (allOk) {
            System.out.println("✨ Todos los servicios están funcionando correctamente");
            return 0;
        } else {
            System.out.println("⚠️ Algunos servicios presentan problemas");
            return 1;
        }
    }

    public static void main(String... args) {
        int exitCode = new CommandLine(new HealthChecker()).execute(args);
        System.exit(exitCode);
    }
}

Uso del Script

  1. Verificar un solo servicio:
jbang HealthChecker.java -u https://jsonplaceholder.typicode.com/
  1. Verificar múltiples servicios:
jbang HealthChecker.java -u https://servicio1.com/health,https://servicio2.com/health
  1. Especificar timeout:
jbang HealthChecker.java -u https://api.ejemplo.com/health -t 60

Ejemplo de Salida

🔍 Iniciando verificación de servicios...

Servicio: https://jsonplaceholder.typicode.com/
Estado: ✅ ACTIVO
Código: 200
------------------------

Servicio: https://api.ejemplo.com/health
Estado: ❌ ERROR
Error: connect timed out
------------------------

⚠️ Algunos servicios presentan problemas

Características del Script

  1. Simplicidad: Un solo archivo que hace una tarea específica y útil.

  2. Funcionalidad DevOps común: Verifica el estado de servicios web.

  3. Salida clara: Usa emojis y formato legible para mejor visualización.

  4. Configurable: Permite especificar URLs y timeout.

  5. Códigos de salida: Retorna 0 si todo está bien, 1 si hay problemas.

Ventajas de este Enfoque

  1. Fácil de mantener: Todo el código está en un solo archivo.

  2. Portable: Se puede ejecutar en cualquier lugar con JBang instalado.

  3. Extensible: Fácil de modificar para agregar más funcionalidades.

  4. Práctico: Útil para monitoreo básico y scripts de CI/CD.

Posibles Mejoras

  1. Agregar soporte para autenticación básica

  2. Incluir verificación de certificados SSL

  3. Generar reportes en diferentes formatos

  4. Agregar notificaciones (email, Slack, etc.)

  5. Incluir métricas de tiempo de respuesta

Este ejemplo es ideal para empezar con JBang en DevOps, ya que:

  • Es fácil de entender y modificar

  • Resuelve un problema común

  • Sirve como base para scripts más complejos


Generador de Reportes Docker

El Generador de Reportes Docker es una herramienta desarrollada con JBang que automatiza la creación de informes detallados sobre los contenedores Docker en tu sistema.

Script para generar reportes sobre contenedores Docker:

///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.github.docker-java:docker-java:3.3.5
//DEPS com.github.docker-java:docker-java-transport-httpclient5:3.3.5
//DEPS org.apache.commons:commons-csv:1.10.0

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.core.DockerClientBuilder;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;

import java.io.FileWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;

public class DockerReport {
    public static void main(String[] args) {
        try (DockerClient dockerClient = DockerClientBuilder.getInstance().build()) {
            List<Container> containers = dockerClient.listContainersCmd()
                .withShowAll(true)
                .exec();

            String fileName = "docker-report-" + 
                DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm")
                    .withZone(ZoneId.systemDefault())
                    .format(Instant.now()) + 
                ".csv";

            try (CSVPrinter csvPrinter = new CSVPrinter(
                    new FileWriter(fileName),
                    CSVFormat.DEFAULT.withHeader(
                        "Container ID",
                        "Names",
                        "Image",
                        "Status",
                        "State",
                        "Created",
                        "Ports"
                    ))) {

                for (Container container : containers) {
                    csvPrinter.printRecord(
                        container.getId(),
                        String.join(", ", container.getNames()),
                        container.getImage(),
                        container.getStatus(),
                        container.getState(),
                        Instant.ofEpochSecond(container.getCreated())
                            .atZone(ZoneId.systemDefault())
                            .toString(),
                        container.getPorts() != null
                            ? container.getPorts().length
                            : 0
                    );
                }
            }

            System.out.println("Reporte generado: " + fileName);
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Como funciona:

jbang DockerReport.java

Resultado

Conclusión

JBang representa un cambio significativo en cómo podemos trabajar con Java y otros lenguajes JVM. Ya sea para prototipado rápido, scripts de utilidad o incluso aplicaciones pequeñas, JBang ofrece una experiencia moderna y eficiente que hace que el desarrollo en Java sea más accesible y ágil.

La combinación de gestión de dependencias integrada, soporte multi-lenguaje, y una comunidad activa que comparte scripts a través de la App Store hace de JBang una herramienta indispensable en el arsenal de cualquier desarrollador Java moderno.

Recursos Útiles

0
Subscribe to my newsletter

Read articles from Rossana Suarez directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rossana Suarez
Rossana Suarez

Soy Roxs 👩‍💻| Software Developer | DevOps | DevSecOps | en @295DevOps 🖼 Content Creator. No se puede crecer si no estas dispuesto a saltar a la zona de peligro 🔥