W3docs

Classpath de Java

Configura el classpath al compilar y ejecutar programas Java para que la JVM encuentre tus clases y dependencias.

El compilador sabe en qué paquetes buscar gracias al classpath — el conjunto de ubicaciones donde Java busca los archivos de clase. Cuando ejecutas java MyApp y obtienes un ClassNotFoundException, la causa casi siempre es el classpath: la JVM no encontró un archivo .class donde esperaba. Entender cómo se construye el classpath transforma el "simplemente no funciona" en un problema preciso y solucionable.

Qué es el classpath

El classpath es una lista ordenada de ubicaciones que la JVM recorre buscando archivos .class. Cada ubicación es una de las siguientes:

  • Un directorio — se toma como la raíz de un árbol de paquetes. La JVM busca com/example/Foo.class dentro de él.
  • Un archivo JAR — se explora como si su árbol de directorios interno fuera un directorio.
  • Un comodín como lib/* — coincide con todos los .jar en lib/ (sin recursividad y sin archivos .class sueltos).

Cuando haces referencia a com.example.Foo, la JVM recorre el classpath en orden y utiliza la primera coincidencia. Si dos ubicaciones contienen la misma clase, la que aparece antes en el classpath gana — una causa habitual de "actualicé el JAR pero sigue ejecutándose el código antiguo."

Configurar el classpath

Hay tres formas de indicar a la JVM qué hay en el classpath, en orden de preferencia:

# 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 anula CLASSPATH. Definir la variable de entorno de forma global es una fuente frecuente de errores — un CLASSPATH obsoleto de un proyecto olvidado hace tiempo provoca comportamientos misteriosos. Prefiere -cp para cada comando.

En Windows, el separador es ;. En macOS y Linux, es :. Pon el valor entre comillas si contiene espacios.

El classpath por defecto

Si no defines uno, la JVM usa el directorio actual (.) como classpath. Por eso javac Hello.java && java Hello funciona de forma inmediata para archivos en el paquete por defecto — Hello.class está justo ahí.

En el momento en que colocas tu código en un paquete, necesitas ejecutar desde el lugar correcto o pasar -cp explícitamente:

# 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

Un error habitual es java -cp out com/example/App. El argumento de java es un nombre de clase, no una ruta — usa puntos, no barras.

El classpath en tiempo de compilación

javac tiene su propio classpath, distinto del que se usa en tiempo de ejecución:

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

-cp aquí lista las ubicaciones donde javac busca los tipos que referencian tus fuentes. Todo lo que esas fuentes importan tiene que estar en lib/dependency.jar o en el classpath implícito. El indicador -d del compilador elige dónde se depositan los archivos .class de salida — normalmente en un árbol paralelo out/.

Para la mayoría de las compilaciones no ejecutas javac y java a mano. Las herramientas de construcción — Maven, Gradle — ensamblan el classpath a partir de las dependencias declaradas. El objetivo de entenderlo manualmente es poder depurar lo que hicieron cuando algo sale mal.

Archivos JAR en el classpath

Un JAR es un archivo ZIP con archivos de clase y metadatos. Ponlo en el classpath y la JVM trata su contenido como otro árbol de paquetes:

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

Algunas notas prácticas:

  • Los comodines solo expanden a JARs: -cp lib/* coincide con todos los .jar en lib/, no con subdirectorios ni archivos .class sueltos.
  • Los comodines no son globs del shell. Los gestiona la propia JVM. La mayoría de los shells expandirían lib/* primero; la JVM espera la cadena literal lib/*. Ponlo entre comillas para evitar la expansión del shell: -cp "lib/*".
  • El orden importa para los duplicados. El primer JAR que proporcione una clase gana.

JARs ejecutables

Si defines un Main-Class en el META-INF/MANIFEST.MF de un JAR, puedes ejecutarlo con solo -jar:

java -jar app.jar

Dos advertencias con -jar:

  • -cp se ignora cuando se usa -jar. La única forma de añadir dependencias al classpath de un JAR ejecutable es mediante el atributo Class-Path: del manifiesto, enumerando otros JARs.
  • El Main-Class del JAR es obligatorio. Sin él, -jar se niega a ejecutar.

Por eso los fat JARs — un único JAR que contiene todas las dependencias, construido con el plugin Maven Shade o el plugin Shadow de Gradle — se han convertido en el estándar. Evitan todo el problema del classpath con los JARs ejecutables.

Depurar problemas de classpath

Dos errores apuntan directamente al classpath y significan cosas ligeramente distintas:

  • ClassNotFoundException — el código solicitó explícitamente una clase por nombre (a menudo mediante Class.forName(...) o reflexión) y el cargador no pudo encontrarla en ningún lugar del classpath.
  • NoClassDefFoundError — la clase estaba presente cuando se compiló tu código, pero falta o no se puede cargar en tiempo de ejecución. La causa habitual es un JAR de dependencia que está en el classpath de compilación pero ausente en el de ejecución.

Cuando ocurre alguno de los dos, sigue esta lista de comprobación:

  1. Imprime el classpath que realmente usó la JVMSystem.getProperty("java.class.path"), como hace el ejemplo a continuación. El conjunto que crees que pasaste y el que está en efecto suelen ser diferentes.
  2. Comprueba el separador. : en macOS/Linux, ; en Windows. Un separador incorrecto une silenciosamente dos entradas en una ruta inválida.
  3. Pon los comodines entre comillas. -cp "lib/*" — un lib/* sin comillas es expandido por el shell antes de que java lo vea.
  4. Recuerda que -jar ignora -cp. Si ejecutas con -jar, el classpath de la línea de comandos se descarta por completo.

La ruta de módulos (un breve desvío)

Desde Java 9, la JVM también dispone de una ruta de módulos (-p o --module-path), paralela al classpath. Los módulos son una unidad de empaquetado más estricta y basada en declaraciones que se superpone a los paquetes. La mayor parte del código de aplicación sigue ejecutándose en el classpath; los módulos son más visibles a nivel del JDK. Puedes ignorarlos mientras aprendes los conceptos básicos y volver a ellos cuando un framework te lo pida.

Un ejemplo práctico

Este programa muestra el classpath desde el interior — lo que la JVM realmente cargó y desde dónde. Usa solo java.lang, por lo que funciona en cualquier lugar.

java— editable, runs on the server

java.class.path informa sobre el classpath de la aplicación; el cargador de clases de String aparece como null porque las clases principales del JDK provienen del cargador bootstrap, no de ninguna entrada del classpath visible por el usuario. La jerarquía de cargadores de clases es el motor que hace funcionar el classpath — sus detalles son tema para un capítulo avanzado.

Qué sigue

Con esto concluyen los paquetes e importaciones. Ahora tienes todas las piezas — nombrado, importación, declaración, localización y la biblioteca estándar que se encuentra al otro extremo de cada línea import. Lo siguiente es una de las decisiones de diseño que definen Java: las excepciones comprobadas y la maquinaria try/catch/finally para gestionar los errores. Continúa con excepciones de Java.

Práctica

Práctica
Un programa Java funciona bien con `java -cp out:lib/dep.jar com.example.App`, pero falla con `java -jar app.jar` aunque `app.jar` tenga `Main-Class` en su manifiesto y empaquete la misma clase `com.example.App`. ¿Por qué?
Un programa Java funciona bien con `java -cp out:lib/dep.jar com.example.App`, pero falla con `java -jar app.jar` aunque `app.jar` tenga `Main-Class` en su manifiesto y empaquete la misma clase `com.example.App`. ¿Por qué?
Was this page helpful?