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.classdentro dele. - Um arquivo JAR — pesquisado como se sua árvore de diretórios interna fosse um diretório.
- Um curinga como
lib/*— corresponde a todo.jaremlib/(não recursivamente, e não arquivos.classsoltos).
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 nameUm 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.MainAlgumas notas práticas:
- Curingas expandem apenas para JARs:
-cp lib/*corresponde a todo.jaremlib/, não a subdiretórios ou arquivos.classsoltos. - 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 literallib/*. 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.jarDois 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 atributoClass-Path:do manifesto, listando outros JARs.- O
Main-Classdo JAR é obrigatório. Sem ele,-jarse 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 viaClass.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:
- Imprima o classpath que a JVM realmente usou —
System.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. - Verifique o separador.
:no macOS/Linux,;no Windows. Um separador errado silenciosamente mescla duas entradas em um caminho inválido. - Coloque curingas entre aspas.
-cp "lib/*"— umlib/*sem aspas é expandido pelo shell antes que ojavao veja. - Lembre-se de que
-jarignora-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.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.