W3docs

Java Classpath

Configure o classpath ao compilar e executar programas Java para que a JVM encontre suas classes e dependências.

O compilador sabe em quais pacotes procurar por causa do classpath — o conjunto de locais onde o Java procura por arquivos de classe. Quando você executa java MyApp e obtém um ClassNotFoundException, a causa é quase sempre o classpath: a JVM não conseguiu encontrar um arquivo .class onde esperava. Entender como o classpath é construído transforma "simplesmente não funciona" em um problema preciso e corrigível.

O que é o classpath

O classpath é uma lista ordenada de locais que a JVM pesquisa por arquivos .class. Cada local é um dos seguintes:

  • Um diretório — tratado como raiz de uma árvore de pacotes. A JVM procura por com/example/Foo.class dentro dele.
  • Um arquivo JAR — pesquisado como se sua árvore de diretórios interna fosse um diretório.
  • Um curinga como lib/* — corresponde a todo .jar em lib/ (não recursivamente, e não arquivos .class soltos).

Quando você referencia com.example.Foo, a JVM percorre o classpath em ordem e usa o primeiro resultado encontrado. Se dois locais contêm a mesma classe, o que aparecer primeiro no classpath vence — uma causa comum de "atualizei o JAR mas o código antigo ainda está rodando."

Definindo o classpath

Há três formas de informar à JVM o que está no classpath, em ordem de preferência:

# 1. -cp / -classpath flag (clearest, scoped to the one command):
java -cp out:lib/mylib.jar com.example.App

# 2. The CLASSPATH environment variable (set once, used by every invocation):
export CLASSPATH=out:lib/mylib.jar
java com.example.App

# 3. JAR manifest Class-Path entry (for executable JARs):
java -jar app.jar

-cp sobrescreve CLASSPATH. Definir a variável de ambiente globalmente é uma fonte frequente de bugs — um CLASSPATH obsoleto de um projeto esquecido há muito tempo causa comportamentos misteriosos. Prefira -cp em cada comando.

No Windows, o separador é ;. No macOS e Linux, é :. Coloque o valor entre aspas se ele contiver espaços.

O classpath padrão

Se você não definir um, a JVM usa o diretório atual (.) como classpath. É por isso que javac Hello.java && java Hello funciona imediatamente para arquivos no pacote padrão — Hello.class está ali mesmo.

No momento em que você coloca seu código em um pacote, você precisa executar a partir do local correto ou passar -cp explicitamente:

# Source: src/com/example/App.java with `package com.example;`
javac -d out src/com/example/App.java
java -cp out com.example.App      # must use the fully-qualified name

Um erro comum é java -cp out com/example/App. O argumento para java é um nome de classe, não um caminho — use pontos, não barras.

O classpath em tempo de compilação

O javac tem seu próprio classpath, distinto do que é usado em tempo de execução:

javac -cp lib/dependency.jar -d out $(find src -name "*.java")

-cp aqui lista os locais onde o javac procura pelos tipos que seus fontes referenciam. Qualquer coisa que esses fontes importam com import precisa estar em lib/dependency.jar ou no classpath implícito. A flag -d do compilador define onde os arquivos .class de saída serão gerados — tipicamente uma árvore paralela out/.

Para a maioria das builds, você não executa javac e java manualmente. Ferramentas de build — Maven, Gradle — montam o classpath a partir das dependências declaradas. O ponto de entender isso manualmente é poder depurar o que elas fizeram quando algo dá errado.

Arquivos JAR no classpath

Um JAR é um arquivo ZIP com arquivos de classe e metadados. Coloque um no classpath e a JVM trata seu conteúdo como outra árvore de pacotes:

java -cp app.jar:lib/json.jar:lib/db.jar com.example.Main

Algumas notas práticas:

  • Curingas expandem apenas para JARs: -cp lib/* corresponde a todo .jar em lib/, não a subdiretórios ou arquivos .class soltos.
  • Curingas não são globs do shell. Eles são tratados pela própria JVM. A maioria dos shells expandiria lib/* primeiro; a JVM espera a string literal lib/*. Coloque entre aspas para evitar a expansão pelo shell: -cp "lib/*".
  • A ordem importa para duplicatas. O primeiro JAR que fornece uma classe vence.

JARs executáveis

Se você definir um Main-Class no META-INF/MANIFEST.MF de um JAR, você pode executá-lo com apenas -jar:

java -jar app.jar

Dois pontos importantes com -jar:

  • -cp é ignorado quando -jar é usado. A única forma de adicionar dependências ao classpath de um JAR executável é por meio do atributo Class-Path: do manifesto, listando outros JARs.
  • O Main-Class do JAR é obrigatório. Sem ele, -jar se recusa a executar.

É por isso que os fat JARs — um único JAR contendo todas as dependências, construído com o plugin Maven Shade ou o plugin Shadow do Gradle — tornaram-se padrão. Eles contornam toda a dança do classpath do JAR executável.

Depurando problemas de classpath

Dois erros apontam diretamente para o classpath, e eles significam coisas ligeiramente diferentes:

  • ClassNotFoundException — o código pediu explicitamente uma classe pelo nome (frequentemente via Class.forName(...) ou reflection) e o loader não conseguiu encontrá-la em nenhum lugar no classpath.
  • NoClassDefFoundError — a classe estava presente quando seu código foi compilado, mas está ausente ou não pode ser carregada em tempo de execução. A causa habitual é uma dependência JAR que está no classpath de compilação, mas ausente do classpath de execução.

Quando você se deparar com qualquer um deles, siga este checklist:

  1. Imprima o classpath que a JVM realmente usouSystem.getProperty("java.class.path"), como o exemplo abaixo faz. O conjunto que você acha que passou e o que está em vigor são frequentemente diferentes.
  2. Verifique o separador. : no macOS/Linux, ; no Windows. Um separador errado silenciosamente mescla duas entradas em um caminho inválido.
  3. Coloque curingas entre aspas. -cp "lib/*" — um lib/* sem aspas é expandido pelo shell antes que o java o veja.
  4. Lembre-se de que -jar ignora -cp. Se você executar com -jar, o classpath da linha de comando é descartado completamente.

O module path (um breve desvio)

Desde o Java 9, a JVM também possui um module path (-p ou --module-path), paralelo ao classpath. Módulos são uma unidade de empacotamento mais rígida e baseada em declarações, construída sobre os pacotes. A maior parte do código de aplicação ainda roda no classpath; os módulos são mais visíveis no nível do JDK. Você pode ignorá-los enquanto aprende o básico e retornar a eles quando um framework pedir.

Um exemplo prático

Este programa mostra o classpath por dentro — o que a JVM realmente carregou e de onde. Ele usa apenas java.lang, então roda em qualquer lugar.

java— editable, runs on the server

java.class.path relata o classpath da aplicação; o class loader de String é impresso como null porque as classes do JDK central vêm do bootstrap loader, não de nenhuma entrada do classpath visível ao usuário. A hierarquia de class loaders é o mecanismo que faz o classpath funcionar — seus detalhes são um tópico para um capítulo avançado.

O que vem a seguir

Isso encerra pacotes e imports. Você agora tem todas as peças — nomenclatura, importação, declaração, localização e a biblioteca padrão que está do outro lado de cada linha import. O próximo tópico é uma das decisões de design definidoras do Java: exceções verificadas e o mecanismo try/catch/finally usado para lidar com erros. Continue para exceções Java.

Prática

Prática
Um programa Java funciona corretamente com `java -cp out:lib/dep.jar com.example.App`, mas falha com `java -jar app.jar` mesmo que `app.jar` tenha `Main-Class` em seu manifesto e empacote a mesma classe `com.example.App`. Por quê?
Um programa Java funciona corretamente com `java -cp out:lib/dep.jar com.example.App`, mas falha com `java -jar app.jar` mesmo que `app.jar` tenha `Main-Class` em seu manifesto e empacote a mesma classe `com.example.App`. Por quê?
Was this page helpful?