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.

Você vai aprender
  • O que é um arquivo JAR (spoiler: é um .zip) e por que ele existe.
  • Criar, listar e extrair um JAR com jar cf, jar tf e jar xf.
  • O que é o MANIFEST.MF e a linha Main-Class.
  • Criar um JAR executável com jar cfe e rodá-lo com java -jar.
  • O erro nenhum atributo de manifesto principal (no main manifest attribute) e como consertar.
  • O que é o classpath, a opção -cp e a linha Class-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, o jar escreveria o resultado na tela.
app.jar
O nome do arquivo que será criado.
*.class
O que vai dentro: todos os .class da pasta (o Bash troca *.class por Main.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)
LinhaO que diz
Manifest-Version: 1.0A 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 do f, depois o do e.
*.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).

A mesma coisa, com nomes longos

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.
A armadilha do Enter que falta

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 do Main-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.jar com a classe br.com.ana.util.Moeda, que formata valores em reais;
  • o programa caixa.jar, com a classe br.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 .class na pasta classes, já nas subpastas do pacote (classes/br/com/ana/util/Moeda.class).
-C classes .
"Entre (change) na pasta classes e ponha tudo (.) de lá." Assim o JAR fica com br/com/ana/… na raiz, e não classes/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

FormaExemploQuando usar
Arquivo-fonte diretojava Main.javaProgramas de um arquivo, estudando e testando (veja javac e java).
Classes numa pastajava -cp classes br.com.ana.loja.CaixaDurante o desenvolvimento, depois do javac.
JAR no classpathjava -cp caixa.jar:util.jar br.com.ana.loja.CaixaQuando o JAR não tem Main-Class, ou para escolher outra classe com main.
JAR executáveljava -jar caixa.jarPara entregar o programa: a pessoa não precisa saber o nome da classe.
Instalador nativojpackage …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

ComandoO que faz
jar cf app.jar *.classCria um JAR com as classes (sem classe principal)
jar cfe app.jar Main *.classCria um JAR executável com Main-Class: Main
jar cfm app.jar manifesto.txt *.classCria 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.jarLista o conteúdo (com detalhes, no v)
jar xf app.jarExtrai tudo na pasta atual
unzip -p app.jar META-INF/MANIFEST.MFMostra o manifesto
java -jar app.jar arg1 arg2Roda a classe do Main-Class, com argumentos
java -cp a.jar:b.jar pacote.ClasseRoda uma classe procurando em vários JARs (; no Windows)
javac -cp lib.jar -d classes Arquivo.javaCompila usando uma biblioteca
Main-Class: pacote.ClasseAtributo do manifesto: classe principal
Class-Path: lib1.jar lib/lib2.jarAtributo 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:

jar

2. Ver o conteúdo

Complete para listar o que tem dentro de loja.jar:

jar

3. Classe principal com pacote

O jar tf mostrou br/com/ana/agenda/App.class. Complete a linha do manifesto:

Main-Class:

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 cf cria, jar tf lista, jar xf extrai. Os argumentos seguem a ordem das letras.
  • O META-INF/MANIFEST.MF é a etiqueta. Main-Class diz por qual classe começar; jar cfe app.jar Main … escreve essa linha.
  • java -jar app.jar roda um JAR executável. Sem Main-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) ou Class-Path no manifesto. Com -jar, o -cp é ignorado.
  • Manifesto escrito à mão: termine a última linha com Enter.
  • jpackage gera instaladores nativos; o Maven e o Gradle fazem o JAR por você.