Java Empacotar em JAR
Até agora, cada programa seu era uma pasta cheia de arquivos .class. Para mandar o programa para outra pessoa, você teria de mandar todos eles, na estrutura de pastas certa. O JAR resolve isso: junta todas as classes num arquivo só, que roda com java -jar programa.jar.
- O que é um arquivo JAR (spoiler: é um
.zip) e por que ele existe. - Criar, listar e extrair um JAR com
jar cf,jar tfejar xf. - O que é o
MANIFEST.MFe a linhaMain-Class. - Criar um JAR executável com
jar cfee rodá-lo comjava -jar. - O erro
nenhum atributo de manifesto principal(no main manifest attribute) e como consertar. - O que é o classpath, a opção
-cpe a linhaClass-Path.
Todos os exemplos acontecem na máquina fictícia notebook, na pasta da usuária ana, com o JDK 25. Se você ainda não sabe compilar com javac e rodar com java, veja antes javac e java; os pacotes (package) estão em Pacotes e import.
A ideia: uma caixa de mudança
Imagine que a Ana vai mudar de casa. Ela poderia carregar cada prato, cada copo e cada livro na mão, um por um. Ou pode pôr tudo em caixas, com uma etiqueta do lado de fora dizendo o que tem dentro ("cozinha: abrir primeiro").
O JAR é essa caixa:
- Os pratos e copos são os arquivos
.class(o programa compilado). - A etiqueta é o manifesto (
MANIFEST.MF): um arquivo de texto dentro do JAR que diz, entre outras coisas, por qual classe começar.
JAR vem de Java ARchive, "arquivo Java" (e é também a palavra inglesa para "pote"). Tecnicamente, é um arquivo ZIP comum, com a extensão trocada para .jar e uma pasta META-INF dentro. Tudo o que você usa em Java "de fora", como bibliotecas, o Maven e o próprio JUnit, chega até você em forma de JAR.
O programa que vamos empacotar
Um programa com duas classes, para o JAR ter mais de um arquivo. A classe Saudacao monta a frase; a Main tem o main e usa a Saudacao.
public class Saudacao {
private final String nome;
public Saudacao(String nome) {
this.nome = nome;
}
public String texto() {
return "Olá, " + nome + "! Este programa veio de um JAR.";
}
}
public class Main {
public static void main(String[] args) {
Saudacao s = new Saudacao("Ana");
System.out.println(s.texto());
}
}
A Ana salvou os dois arquivos na pasta ~/jar-aula e compilou. O javac *.java compila todos os .java da pasta de uma vez:
$ cd jar-aula $ javac *.java $ ls Main.class Main.java Saudacao.class Saudacao.java
São dois .class. É isso que vai para a caixa (os .java são o código-fonte; quem roda o programa não precisa deles).
Missão: este é o programa que vai virar JAR. Para caber numa caixa só do editor, as duas classes estão no mesmo arquivo (a Saudacao sem public; o javac gera mesmo assim dois .class). Faça a saudação ser para o Bruno e clique em Rodar.
public class Main {
public static void main(String[] args) {
Saudacao s = new Saudacao("Ana");
System.out.println(s.texto());
}
}
class Saudacao {
private final String nome;
Saudacao(String nome) {
this.nome = nome;
}
String texto() {
return "Olá, " + nome + "! Este programa veio de um JAR.";
}
}
public class Main {
public static void main(String[] args) {
Saudacao s = new Saudacao("Bruno");
System.out.println(s.texto());
}
}
class Saudacao {
private final String nome;
Saudacao(String nome) {
this.nome = nome;
}
String texto() {
return "Olá, " + nome + "! Este programa veio de um JAR.";
}
}
1. Criar o JAR: jar cf
O JDK traz um programa chamado jar, que funciona parecido com o tar do Linux: as letras logo depois do nome dizem o que fazer.
- jar
- A ferramenta do JDK que cria e lê arquivos JAR.
- c
- create, "criar" um JAR novo.
- f
- file, "arquivo": o próximo argumento é o nome do JAR. Sem o
f, ojarescreveria o resultado na tela. - app.jar
- O nome do arquivo que será criado.
- *.class
- O que vai dentro: todos os
.classda pasta (o Bash troca*.classporMain.class Saudacao.class).
$ jar cf app.jar *.class $ ls -l app.jar -rw-rw-r-- 1 ana ana 1352 out 16 18:18 app.jar « um arquivo só, de 1352 bytes
O jar não mostra nada quando dá certo (como a maioria dos comandos do Linux). O arquivo app.jar apareceu.
Ver o que tem dentro: jar tf
Trocando o c por t (table, "tabela de conteúdo"), o jar lista o que está na caixa:
$ jar tf app.jar META-INF/ « pasta criada pelo jar META-INF/MANIFEST.MF « a etiqueta da caixa Main.class Saudacao.class
Além das suas duas classes, o jar criou sozinho a pasta META-INF (de meta-information, "informação sobre o próprio pacote") com o arquivo MANIFEST.MF. Com a letra v (verbose, "detalhado"), aparecem também o tamanho e a data de cada arquivo:
$ jar tvf app.jar
0 Fri Oct 16 18:18:48 GMT-03:00 2026 META-INF/
84 Fri Oct 16 18:18:48 GMT-03:00 2026 META-INF/MANIFEST.MF
486 Fri Oct 16 18:18:46 GMT-03:00 2026 Main.class
836 Fri Oct 16 18:18:46 GMT-03:00 2026 Saudacao.class
Prova de que é um zip
Qualquer programa de ZIP abre um JAR. O unzip -l do Linux lista o conteúdo, igualzinho:
$ unzip -l app.jar
Archive: app.jar
Length Date Time Name
--------- ---------- ----- ----
0 2026-10-16 18:18 META-INF/
66 2026-10-16 18:18 META-INF/MANIFEST.MF
486 2026-10-16 18:18 Main.class
836 2026-10-16 18:18 Saudacao.class
--------- -------
1388 4 files
No Windows, se você renomear app.jar para app.zip, o Explorer abre como uma pasta comum. É só um zip.
Tirar da caixa: jar xf
O x (extract, "extrair") faz o caminho contrário e solta os arquivos na pasta atual. Por isso a Ana cria uma pasta vazia antes:
$ mkdir extraido && cd extraido && jar xf ../app.jar && ls -R && cd .. .: Main.class META-INF Saudacao.class ./META-INF: MANIFEST.MF
2. Rodar o JAR: a primeira tentativa dá erro
Para rodar um JAR, a forma natural é java -jar. Veja o que acontece com o JAR que acabamos de criar:
$ java -jar app.jar nenhum atributo de manifesto principal em app.jar $ echo $? 1 « código de saída 1: deu erro
Em computadores configurados em inglês, a mesma mensagem aparece assim:
$ java -jar app.jar no main manifest attribute, in app.jar
O que ela quer dizer: "o manifesto deste JAR não diz qual é a classe principal". O Java abriu a caixa, leu a etiqueta e não achou a linha "comece por aqui". Ele não tenta adivinhar: um JAR pode ter centenas de classes, várias com main. Para entender, vamos olhar a etiqueta.
O manifesto: META-INF/MANIFEST.MF
O unzip -p mostra o conteúdo de um arquivo de dentro do zip, sem extrair:
$ unzip -p app.jar META-INF/MANIFEST.MF Manifest-Version: 1.0 Created-By: 25.0.4.1 (Eclipse Adoptium)
| Linha | O que diz |
|---|---|
Manifest-Version: 1.0 | A versão do formato do manifesto (é sempre 1.0). |
Created-By: 25.0.4.1 (Eclipse Adoptium) | Quem criou o JAR: o jar do JDK 25.0.4.1 da Adoptium (o Temurin). |
Cada linha é um atributo no formato Nome: valor. Falta justamente o atributo que o java -jar procura: Main-Class ("classe principal").
3. JAR executável: jar cfe
A letra e (entry point, "ponto de entrada") manda o jar escrever a linha Main-Class no manifesto. O nome da classe vem logo depois do nome do JAR:
- c f e
- Criar, num arquivo, com ponto de entrada.
- app.jar
- O nome do JAR (é o argumento do
f). - Main
- A classe principal (é o argumento do
e). Os argumentos vêm na mesma ordem das letras: primeiro o dof, depois o doe. - *.class
- O conteúdo.
$ jar cfe app.jar Main *.class $ unzip -p app.jar META-INF/MANIFEST.MF Manifest-Version: 1.0 Created-By: 25.0.4.1 (Eclipse Adoptium) Main-Class: Main « a linha nova $ java -jar app.jar Olá, Ana! Este programa veio de um JAR.
Funcionou. Esse app.jar é um JAR executável: qualquer pessoa com Java instalado roda com java -jar app.jar, sem saber quais classes existem lá dentro. Em muitos ambientes gráficos, dar dois cliques num JAR executável também o abre (para programas com janela).
O jar também aceita opções por extenso, mais fáceis de ler num script:
$ jar --create --file app4.jar --main-class Main Main.class Saudacao.class $ java -jar app4.jar Olá, Ana! Este programa veio de um JAR.
--create = c, --file = f, --main-class = e, --list = t, --extract = x.
Outra forma: escrever o manifesto à mão (jar cfm)
Você também pode escrever um arquivo de texto com as linhas que quer acrescentar ao manifesto e entregá-lo com a letra m (manifest). Essa forma é útil quando há mais de um atributo, como você vai ver no Class-Path.
$ printf 'Main-Class: Main\n' > manifesto.txt « um arquivo com uma linha (e o Enter no fim) $ jar cfm app2.jar manifesto.txt Main.class Saudacao.class $ java -jar app2.jar Olá, Ana! Este programa veio de um JAR.
O manifesto exige que toda linha termine com uma quebra de linha, inclusive a última. Se o seu arquivo terminar sem Enter, a última linha é ignorada em silêncio:
$ printf 'Main-Class: Main' > sem-enter.txt « sem o \n no fim $ jar cfm app3.jar sem-enter.txt Main.class Saudacao.class $ java -jar app3.jar nenhum atributo de manifesto principal em app3.jar
O jar não reclama; o erro só aparece na hora de rodar. Se você escreve o manifesto num editor, aperte Enter depois da última linha.
4. O classpath: onde o Java procura as classes
Quando o programa usa a classe Saudacao, a JVM precisa achar o arquivo Saudacao.class. A lista de lugares (pastas e JARs) onde ela procura se chama classpath ("caminho das classes"). Pense numa lista de endereços que o carteiro percorre até achar o destinatário.
- Se você não diz nada, o classpath é só a pasta atual (
.). - Com a opção
-cp(ou-classpath, ou--class-path), você dá a lista. - Com
java -jar, o classpath é o próprio JAR (mais o que o manifesto disser).
Veja a diferença. Na pasta de cima (~), o Main.class não está na pasta atual:
$ cd .. $ java Main Erro: Não foi possível localizar nem carregar a classe principal Main Causada por: java.lang.ClassNotFoundException: Main $ java -cp jar-aula Main « procure na pasta jar-aula Olá, Ana! Este programa veio de um JAR.
Um JAR também pode estar no classpath, mesmo sem Main-Class. Lembra do primeiro app.jar, que deu erro com java -jar? Ele roda se você disser qual classe usar:
$ java -cp app.jar Main Olá, Ana! Este programa veio de um JAR.
- -cp app.jar
- "Procure as classes dentro de
app.jar." - Main
- "Comece pela classe
Main." Aqui é você quem faz o papel doMain-Class.
Quando falta uma classe no JAR
Se a caixa foi montada sem uma das peças, o programa começa a rodar e só quebra quando precisa da peça que falta:
$ jar cf sem-saudacao.jar Main.class « esqueceu a Saudacao.class $ java -cp sem-saudacao.jar Main Exception in thread "main" java.lang.NoClassDefFoundError: Saudacao at Main.main(Main.java:3) Caused by: java.lang.ClassNotFoundException: Saudacao at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490) ... 1 more
Leia de cima para baixo: NoClassDefFoundError: Saudacao ("não achei a definição da classe Saudacao"), na linha 3 do Main.java (o new Saudacao("Ana")). A causa (Caused by) é a ClassNotFoundException: a classe não estava em nenhum lugar do classpath. Para ler esse tipo de mensagem com calma, veja O que são Exceções.
5. Um projeto de verdade: pacotes e uma biblioteca
Programas reais têm classes em pacotes e usam bibliotecas (JARs feitos por outras pessoas). A Ana separou o seu caixa de loja em duas partes:
- uma "biblioteca"
util.jarcom a classebr.com.ana.util.Moeda, que formata valores em reais; - o programa
caixa.jar, com a classebr.com.ana.loja.Caixa, que soma os preços recebidos nos argumentos.
package br.com.ana.util;
public class Moeda {
public static String formatar(double valor) {
return String.format("R$ %.2f", valor);
}
}
package br.com.ana.loja;
import br.com.ana.util.Moeda;
public class Caixa {
public static void main(String[] args) {
double total = 0;
for (String a : args) {
total += Double.parseDouble(a);
}
System.out.println("Itens: " + args.length);
System.out.println("Total: " + Moeda.formatar(total));
}
}
Passo 1: a biblioteca
$ javac -d classes src/br/com/ana/util/Moeda.java $ jar cf util.jar -C classes . $ jar tf util.jar META-INF/ META-INF/MANIFEST.MF br/ br/com/ br/com/ana/ br/com/ana/util/ br/com/ana/util/Moeda.class
- javac -d classes
- Compila e põe os
.classna pastaclasses, já nas subpastas do pacote (classes/br/com/ana/util/Moeda.class). - -C classes .
- "Entre (change) na pasta
classese ponha tudo (.) de lá." Assim o JAR fica combr/com/ana/…na raiz, e nãoclasses/br/…. A estrutura de pastas dentro do JAR precisa ser igual ao nome do pacote.
Passo 2: compilar o programa usando a biblioteca
O javac também tem classpath. Sem dizer onde está a Moeda, ele não compila:
$ rm -r classes
$ javac -d classes src/br/com/ana/loja/Caixa.java
src/br/com/ana/loja/Caixa.java:3: error: package br.com.ana.util does not exist
import br.com.ana.util.Moeda;
^
src/br/com/ana/loja/Caixa.java:12: error: cannot find symbol
System.out.println("Total: " + Moeda.formatar(total));
^
symbol: variable Moeda
location: class Caixa
2 errors
$ javac -cp util.jar -d classes src/br/com/ana/loja/Caixa.java « agora sim
$ jar cfe caixa.jar br.com.ana.loja.Caixa -C classes .
Repare que o Main-Class usa o nome completo da classe, com o pacote: br.com.ana.loja.Caixa.
Passo 3: rodar (e o erro clássico)
$ java -jar caixa.jar 4.50 10 2.25 Itens: 3 Exception in thread "main" java.lang.NoClassDefFoundError: br/com/ana/util/Moeda at br.com.ana.loja.Caixa.main(Caixa.java:12) Caused by: java.lang.ClassNotFoundException: br.com.ana.util.Moeda at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490) ... 1 more
O programa começou (mostrou Itens: 3) e quebrou na linha 12, a primeira que usa a Moeda: a biblioteca não está no classpath. Há duas soluções.
Solução A: dar o classpath na linha de comando. Vários lugares são separados por : no Linux e no macOS (no Windows, por ;). Com -cp, você também diz a classe principal:
$ java -cp caixa.jar:util.jar br.com.ana.loja.Caixa 4.50 10 2.25 Itens: 3 Total: R$ 16,75
(A vírgula em 16,75 vem do idioma do computador, pt_BR; veja Formatar Texto.)
-cp e -jar não se misturam
Com -jar, o Java ignora o -cp, sem avisar. Por isso java -cp util.jar -jar caixa.jar dá o mesmo NoClassDefFoundError. Ou você usa -cp com o nome da classe, ou usa -jar com o Class-Path no manifesto.
Solução B: escrever Class-Path no manifesto. O atributo Class-Path lista outros JARs, relativos à pasta onde o JAR está, separados por espaço:
$ printf 'Main-Class: br.com.ana.loja.Caixa\nClass-Path: util.jar\n' > manifesto.txt $ jar cfm caixa.jar manifesto.txt -C classes . $ java -jar caixa.jar 4.50 10 2.25 Itens: 3 Total: R$ 16,75
Agora basta entregar os dois arquivos, caixa.jar e util.jar, na mesma pasta. Em projetos com dezenas de bibliotecas, ninguém faz isso à mão: o Maven e o Gradle baixam as bibliotecas, montam o classpath e criam o JAR.
Missão: este é um caixa simplificado, sem pacote. Ele já recebe os preços 4.50 10 2.25 como argumentos (o que viria depois de java -jar caixa.jar). Complete o laço para descobrir o maior preço e mostrar Maior: 10.0.
public class Main {
public static void main(String[] args) {
double maior = 0;
for (String a : args) {
double v = Double.parseDouble(a);
// compare v com maior aqui
}
System.out.println("Itens: " + args.length);
System.out.println("Maior: " + maior);
}
}
public class Main {
public static void main(String[] args) {
double maior = 0;
for (String a : args) {
double v = Double.parseDouble(a);
if (v > maior) {
maior = v;
}
}
System.out.println("Itens: " + args.length);
System.out.println("Maior: " + maior);
}
}
6. Formas diferentes de rodar um programa
| Forma | Exemplo | Quando usar |
|---|---|---|
| Arquivo-fonte direto | java Main.java | Programas de um arquivo, estudando e testando (veja javac e java). |
| Classes numa pasta | java -cp classes br.com.ana.loja.Caixa | Durante o desenvolvimento, depois do javac. |
| JAR no classpath | java -cp caixa.jar:util.jar br.com.ana.loja.Caixa | Quando o JAR não tem Main-Class, ou para escolher outra classe com main. |
| JAR executável | java -jar caixa.jar | Para entregar o programa: a pessoa não precisa saber o nome da classe. |
| Instalador nativo | jpackage … | Para quem não tem Java instalado (veja abaixo). |
Uma palavra sobre o jpackage
Desde o Java 16, o JDK traz o jpackage, que transforma o seu JAR num instalador nativo: .deb ou .rpm no Linux, .msi ou .exe no Windows, .dmg no macOS. O instalador leva junto uma cópia enxuta do Java, então quem instala nem precisa ter o Java no computador.
$ jpackage --version 25.0.4.1
Ele exige ferramentas de cada sistema (por exemplo, o dpkg para gerar .deb) e tem muitas opções, por isso fica fora desta aula. A ideia por trás dele, o Java "enxuto", é o jlink, que você vê em Módulos (JPMS).
Quando dá errado
nenhum atributo de manifesto principal em app.jar
$ java -jar app.jar nenhum atributo de manifesto principal em app.jar
O que aconteceu: o manifesto não tem Main-Class (em inglês: no main manifest attribute, in app.jar). Ou o JAR foi criado com jar cf, sem o e, ou o arquivo do manifesto escrito à mão não terminava com Enter.
Como confirmar: unzip -p app.jar META-INF/MANIFEST.MF e procure a linha Main-Class.
Como resolver: recrie com jar cfe app.jar Main *.class, ou rode sem -jar: java -cp app.jar Main.
Não foi possível localizar nem carregar a classe principal
$ jar cfe errado.jar Principal *.class $ java -jar errado.jar Erro: Não foi possível localizar nem carregar a classe principal Principal Causada por: java.lang.ClassNotFoundException: Principal
O que aconteceu: o Main-Class aponta para uma classe que não está no JAR. O jar não confere o nome. Erros comuns: digitar errado, esquecer o pacote (Caixa em vez de br.com.ana.loja.Caixa) ou escrever Main.class (o certo é só Main).
Como resolver: veja com jar tf o caminho do .class, troque as / por ., tire o .class: br/com/ana/loja/Caixa.class → br.com.ana.loja.Caixa.
NoClassDefFoundError no meio do programa
O que aconteceu: o programa começou, mas uma classe usada mais adiante não está no classpath: faltou pôr no JAR, faltou o -cp da biblioteca, ou você usou -cp junto com -jar (que ignora o -cp).
Como resolver: confira com jar tf se a classe está lá; para bibliotecas, use java -cp app.jar:lib.jar Classe ou Class-Path no manifesto.
package … does not exist ao compilar
O que aconteceu: o javac não achou a biblioteca. O classpath vale para o javac também.
Como resolver: javac -cp util.jar ….
Referência
| Comando | O que faz |
|---|---|
jar cf app.jar *.class | Cria um JAR com as classes (sem classe principal) |
jar cfe app.jar Main *.class | Cria um JAR executável com Main-Class: Main |
jar cfm app.jar manifesto.txt *.class | Cria um JAR acrescentando as linhas de manifesto.txt ao manifesto |
jar cf app.jar -C classes . | Põe o conteúdo da pasta classes na raiz do JAR |
jar tf app.jar / jar tvf app.jar | Lista o conteúdo (com detalhes, no v) |
jar xf app.jar | Extrai tudo na pasta atual |
unzip -p app.jar META-INF/MANIFEST.MF | Mostra o manifesto |
java -jar app.jar arg1 arg2 | Roda a classe do Main-Class, com argumentos |
java -cp a.jar:b.jar pacote.Classe | Roda uma classe procurando em vários JARs (; no Windows) |
javac -cp lib.jar -d classes Arquivo.java | Compila usando uma biblioteca |
Main-Class: pacote.Classe | Atributo do manifesto: classe principal |
Class-Path: lib1.jar lib/lib2.jar | Atributo do manifesto: outros JARs, relativos ao JAR |
Exercícios
1. Criar um JAR executável
Complete para criar loja.jar com todas as classes da pasta e a classe principal Loja:
2. Ver o conteúdo
Complete para listar o que tem dentro de loja.jar:
3. Classe principal com pacote
O jar tf mostrou br/com/ana/agenda/App.class. Complete a linha do manifesto:
4. O que é, por dentro, um arquivo .jar?
5. java -jar app.jar mostrou nenhum atributo de manifesto principal em app.jar. Qual comando roda o programa sem recriar o JAR, sabendo que a classe com main é Main?
6. O caixa.jar precisa do util.jar. Qual destes comandos não encontra a biblioteca?
Resumo
- Um JAR é um ZIP com as classes compiladas e a pasta
META-INF; serve para entregar o programa (ou uma biblioteca) num arquivo só. jar cfcria,jar tflista,jar xfextrai. Os argumentos seguem a ordem das letras.- O
META-INF/MANIFEST.MFé a etiqueta.Main-Classdiz por qual classe começar;jar cfe app.jar Main …escreve essa linha. java -jar app.jarroda um JAR executável. SemMain-Class:nenhum atributo de manifesto principal(no main manifest attribute).- O classpath é a lista de pastas e JARs onde o Java procura classes:
-cp a.jar:b.jar(;no Windows) ouClass-Pathno manifesto. Com-jar, o-cpé ignorado. - Manifesto escrito à mão: termine a última linha com Enter.
jpackagegera instaladores nativos; o Maven e o Gradle fazem o JAR por você.