Tu primera aplicación CLI con Quarkus y Picocli

Quarkus no es solo para microservicios y APIs REST. También puedes crear aplicaciones de línea de comandos: herramientas que ejecutas desde la terminal, hacen su trabajo, y terminan.

¿Para qué sirven las CLIs? En el mundo empresarial y de desarrollo, son más comunes de lo que parece:

  • Herramientas de diagnóstico: verificar que servicios estén activos, validar configuración antes de un despliegue, chequear conectividad
  • Procesamiento de datos: leer archivos CSV, transformarlos, generar reportes
  • Clientes de APIs internas: un wrapper que simplifica operaciones frecuentes contra tus propios servicios
  • Automatización de tareas: lo que antes era un script Bash difícil de mantener, ahora puede ser Java tipado y testeable
  • Utilidades de desarrollo: generadores de código, validadores, formateadores

Lo interesante de hacerlas con Quarkus es que puedes compilarlas como ejecutables nativos. Eso significa arranque veloz (milisegundos, no segundos) y un único archivo que puedes distribuir sin necesidad de que el usuario tenga Java instalado.


Un ejemplo práctico: generador de UUIDs

Vamos a construir un generador de UUIDs con un subcomando que sirva para validar:

$ uuid
a3f8b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c

$ uuid --cantidad 3
a3f8b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c
b4e9c3d2-5f6a-7b8c-9d0e-1f2a3b4c5d6e
c5f0d4e3-6a7b-8c9d-0e1f-2a3b4c5d6e7f

$ uuid validar a3f8b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c
 UUID válido

$ uuid validar esto-no-es-un-uuid
 No es un UUID válido

Es un ejemplo básico, pero nos va a servir para aprender las funcionalidades principales de Picocli.


Picocli: el framework detrás de las CLIs en Java

Picocli es el framework más popular para crear aplicaciones de línea de comandos en Java. Quarkus lo integra de forma nativa, lo que significa que podemos usar todas sus funcionalidades y además compilar a nativo.

¿Qué nos da Picocli?

  • Opciones y flags: parámetros como --cantidad 5, --mayusculas
  • Subcomandos: estructuras como git commit, podman run
  • Ayuda automática: el clásico --help sin escribir código
  • Validación de argumentos: si alguien pone --cantidad tres, Picocli avisa que esperaba un número

Creando el proyecto

Para aplicaciones CLI usamos quarkus create cli (en lugar del habitual quarkus create app):

quarkus create cli io.quarkiverso:uuid-generator --no-code && \
cd uuid-generator

El --no-code evita que Quarkus genere código de ejemplo.

La estructura es la típica de Quarkus con la diferencia que la extensión picocli ya viene incluida.

Antes de escribir código, configura src/main/resources/application.properties:

# Salida limpia (sin banner ni logs)
quarkus.banner.enabled=false
quarkus.log.level=WARN

Esto hará que nuestro comando uuid muestre solo el resultado en la salida, sin el banner de Quarkus ni mensajes de log de nivel INFO.


El comando principal

Crea el archivo UuidCommand.java en src/main/java/io/quarkiverso/:

package io.quarkiverso;

import java.util.UUID;
import io.quarkus.picocli.runtime.annotations.TopCommand;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

@TopCommand
@Command(
    name = "uuid",
    description = "Genera identificadores únicos (UUID)",
    mixinStandardHelpOptions = true
)
public class UuidCommand implements Runnable {

    @Option(
        names = {"-c", "--cantidad"},
        description = "Cuántos UUIDs generar",
        defaultValue = "1"
    )
    int cantidad;

    @Option(
        names = {"-u", "--mayusculas"},
        description = "Genera UUIDs en mayúsculas"
    )
    boolean mayusculas;

    @Override
    public void run() {
        for (int i = 0; i < cantidad; i++) {
            String uuid = UUID.randomUUID().toString();
            System.out.println(mayusculas ? uuid.toUpperCase() : uuid);
        }
    }
}

Veamos qué hace cada parte:

ElementoQué hace
@TopCommandIndica a Quarkus que este es el comando principal
@CommandDefine que esta clase es un comando ejecutable
name = "uuid"El nombre que aparece en la ayuda
mixinStandardHelpOptions = trueAgrega --help y --version automáticamente
@OptionDefine parámetros configurables

La anotación @Option tiene:

  • names: las formas de invocar la opción (-c corta, o --cantidad larga)
  • description: aparece en el --help
  • defaultValue: valor si no se especifica

Las opciones booleanas como --mayusculas no necesitan valor. Si están presentes, son true.

En este punto ya puedes probar. Compila con:

quarkus build

Y ejecuta:

java -jar target/quarkus-app/quarkus-run.jar

El subcomando validar

Ahora agreguemos un subcomando para validar UUIDs. Crea ValidateCommand.java:

package io.quarkiverso;

import java.util.UUID;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

@Command(
    name = "validar",
    description = "Verifica si un string es un UUID válido"
)
public class ValidateCommand implements Runnable {

    @Parameters(
        index = "0",
        description = "El string a validar"
    )
    String input;

    @Override
    public void run() {
        try {
            UUID.fromString(input);
            System.out.println("✅ UUID válido");
        } catch (IllegalArgumentException e) {
            System.out.println("❌ No es un UUID válido");
        }
    }
}

Aquí usamos @Parameters en lugar de @Option. La diferencia es que los parámetros son posicionales, como en git commit -m "mensaje" donde «mensaje» es un parámetro.

Ahora conectamos el subcomando al comando principal. En UuidCommand.java, agrega el elemento subcommands (presta atención a los dos comentarios):

@TopCommand
@Command(
    name = "uuid",
    description = "Genera y valida identificadores únicos (UUID)",
    mixinStandardHelpOptions = true, // <-- agregar la , al final
    subcommands = { ValidateCommand.class }  // <-- agregar esta línea
)
public class UuidCommand implements Runnable {

Probando la herramienta

Compila el proyecto:

quarkus build

Y ejecuta:

# Genera un UUID
java -jar target/quarkus-app/quarkus-run.jar

# Genera cinco UUIDs
java -jar target/quarkus-app/quarkus-run.jar -c 5

# En mayúsculas
java -jar target/quarkus-app/quarkus-run.jar -u

# Combinando opciones
java -jar target/quarkus-app/quarkus-run.jar -c 3 -u

# Validar un UUID
java -jar target/quarkus-app/quarkus-run.jar validar a3f8b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c

# Validar algo inválido
java -jar target/quarkus-app/quarkus-run.jar validar hola-mundo

Probando la ayuda

Ejecuta --help para ver la documentación automática:

# Ayuda general
java -jar target/quarkus-app/quarkus-run.jar --help

# Ayuda del subcomando
java -jar target/quarkus-app/quarkus-run.jar validar --help

Todo esto lo genera Picocli a partir de las anotaciones. No escribimos ni una línea de código para la documentación.


Validación automática de Picocli

Una de las ventajas de Picocli es que valida los argumentos automáticamente. Si alguien se equivoca, el error es claro:

java -jar target/quarkus-app/quarkus-run.jar --cantidad tres

Invalid value for option '--cantidad': 'tres' is not an int

No tuvimos que escribir código para eso. Picocli sabe que cantidad es un int y valida la entrada.


Compilando a ejecutable nativo

Hasta ahora usamos java -jar, que funciona pero tarda casi un segundo en arrancar porque levanta la JVM. La ventaja de Quarkus es que podemos compilar un ejecutable nativo.

Para compilar a nativo, ejecuta:

quarkus build --native

La compilación nativa tarda un poco, pero el resultado vale la pena. El ejecutable queda en target/:

./target/uuid-generator-1.0.0-SNAPSHOT-runner -c 3

Para usarlo como mostramos al principio (uuid -c 3), renómbralo de la siguiente manera para que el ejecutable nativo quede en la raíz del proyecto:

# Linux / macOS
mv target/uuid-generator-1.0.0-SNAPSHOT-runner uuid
./uuid -c 3

# Windows
rename target\uuid-generator-1.0.0-SNAPSHOT-runner.exe uuid.exe
uuid.exe -c 3

La diferencia en velocidad de arranque es notable:

# Con JVM
time java -jar target/quarkus-app/quarkus-run.jar
# 0.204 total

# Nativo
time ./uuid
# 0.020 total

El ejecutable nativo arranca, en este caso, unas 10 veces más rápido. Para una herramienta que se ejecuta frecuentemente, eso marca la diferencia.

El ejecutable es autónomo: no necesita Java instalado ni librerías extra. Puedes copiarlo a cualquier lugar de tu sistema.


Pruebas

Como vimos en Testing en Quarkus: La guía definitiva, una aplicación bien construida incluye pruebas. El proyecto de este artículo no es la excepción.

En el repositorio git disponible al final de este artículo 👇, incluyo dos tipos de tests:

  • UuidCommandTest con @QuarkusTest: pruebas rápidas que se ejecutan en JVM durante el desarrollo
  • UuidCommandIT con @QuarkusIntegrationTest: pruebas contra el ejecutable nativo compilado

Ambos validan los mismos escenarios: generación de UUIDs, opciones combinadas, validación correcta e incorrecta, y manejo de errores en argumentos.

# Tests unitarios
./mvnw test

# Tests de integración (compila el nativo automáticamente)
./mvnw verify -Dnative

Resumen

Desde la línea de comandos también se explora el Quarkiverso. En este artículo creamos una CLI con Quarkus, aprendemos a estructurar comandos reales y la convertimos en un ejecutable nativo listo para usar:

ConceptoPara qué sirve
@TopCommandIndica el comando principal a Quarkus
@CommandDefine comandos y subcomandos
@OptionParámetros con nombre (--cantidad)
@ParametersParámetros posicionales
subcommandsEstructura jerárquica de comandos
mixinStandardHelpOptions--help y --version automáticos
quarkus build --nativeEjecutable instantáneo

El código completo está disponible en el repositorio del Quarkiverso.