jbundle: distribuindo aplicações JVM como binário sem o inferno do GraalVM
Quem trabalha com JVM (Java, Clojure, Kotlin, Scala) conhece o problema: você quer distribuir sua aplicação como um binário único, sem exigir que o usuário tenha Java instalado.
A "solução" oficial é o GraalVM native-image. Na teoria, compila pra binário nativo. Na prática:
- Build de 10+ minutos
- Configuração de reflection pra cada lib que usa reflection (spoiler: quase todas)
- Libs incompatíveis que simplesmente não funcionam
- Toolchain complexo que quebra entre versões
Cansei disso. Criei o jbundle.
O que é
Uma ferramenta em Rust que empacota sua aplicação JVM + um runtime mínimo (via jlink) num binário único e autocontido.
projeto/jar → jbundle → binário único (roda em qualquer lugar)
Sem Java instalado no destino. Sem configuração de reflection. Sem libs incompatíveis. Funciona exatamente como funciona no seu ambiente de dev.
Como funciona
- Detecta o build system (deps.edn, project.clj, pom.xml, build.gradle)
- Builda o JAR
- Baixa JDK da Adoptium (fica em cache)
- Detecta módulos necessários com
jdeps - Cria runtime mínimo com
jlink(~30-50 MB) - Empacota tudo num binário multi-camada
O binário resultante usa cache por hash de conteúdo. Atualizou só o código da app? O runtime cached é reaproveitado.
Comparativo honesto
| jbundle | GraalVM native-image | |
|---|---|---|
| Compatibilidade | 100% JVM | Requer config de reflection, libs não suportadas |
| Tempo de build | Rápido (jlink + packaging) | Lento (compilação AOT) |
| Tamanho do binário | ~30-50 MB | ~20-40 MB |
| Startup (após 1ª execução) | ~200-350ms | ~10-50ms |
| Primeira execução | Extrai + gera CDS (~2-5s extra) | Instantâneo |
| Setup | Só o jbundle | GraalVM + native-image + configs |
| Debug | Tooling JVM padrão | Limitado |
Sim, GraalVM ganha em startup e tamanho. Mas jbundle ganha em tudo que importa no dia a dia: compatibilidade total, build rápido, zero configuração.
Uso
# A partir de um projeto Clojure
jbundle build --input ./meu-projeto --output ./dist/app
# A partir de um projeto Java/Kotlin/Scala
jbundle build --input ./meu-projeto --output ./dist/app
# A partir de um JAR pronto
jbundle build --input ./target/app.jar --output ./dist/app
# Executar — não precisa de Java instalado
./dist/app
Performance de startup
A primeira execução é mais lenta (~2-5s) porque extrai as camadas e gera o AppCDS cache. Execuções subsequentes usam o cache:
- Profile Server: ~400-600ms (padrão, pra serviços)
- RaC (Linux): ~10-50ms (checkpoint/restore, quase nativo)
Instalação
git clone https://github.com/avelino/jbundle.git
cd jbundle
cargo install --path .
Por que Rust?
Porque tooling de build precisa ser rápido e sem dependências. Irônico usar Rust pra resolver um problema de distribuição Java? Talvez. Mas funciona.
Link: https://github.com/avelino/jbundle
Se você já sofreu com GraalVM, experimenta. Se funcionar pro seu caso de uso, uma star ajuda o projeto a ganhar visibilidade.
Feedbacks e contribuições são bem-vindos — especialmente suporte a Windows, que ainda não tá implementado.