Introducción a los módulos de Java (JPMS)
Qué son los módulos en Java, los problemas que resuelve JPMS y cómo se relaciona con el classpath.
El Java Platform Module System (JPMS), introducido en Java 9, añade una capa por encima de los paquetes. Un módulo es un grupo de paquetes con nombre y autodescriptivo que declara explícitamente dos cosas: qué necesita de otros módulos y qué ofrece a ellos. Esa declaración reside en un único archivo, module-info.java, en la raíz del módulo. Esta parte del libro lo explica en detalle; este capítulo explica por qué existe.
El problema que JPMS resuelve: el classpath
Antes de Java 9, todos los JAR se colocaban en un único classpath plano. Ese esquema tenía problemas crónicos:
- Sin encapsulación. Cada clase
publicde cada JAR era accesible para todos. Una clase pensada solo como ayudante interno (sun.misc.Unsafe,com.example.internal.*) podía ser usada por cualquiera, por lo que nunca podía cambiarse de forma segura. - Sin dependencias declaradas. Un JAR nunca indicaba qué otros JAR necesitaba. Una dependencia faltante solo se descubría cuando un
NoClassDefFoundErrorexplotaba en tiempo de ejecución — posiblemente en producción. - JAR hell. Dos JAR que proporcionaban el mismo paquete, o dos versiones de la misma biblioteca, se fusionaban silenciosamente según el orden del classpath. Ganaba la clase que se cargara primero.
Los módulos atacan los tres problemas: un módulo oculta cada paquete que no exporta explícitamente, declara cada módulo que requires, y la JVM verifica el grafo completo al arrancar — los módulos faltantes o duplicados fallan rápidamente.
Módulos vs. paquetes vs. JAR
Estos tres conceptos son fáciles de confundir:
| Concepto | Lo que agrupa | Regla de visibilidad |
|---|---|---|
| Package | clases | public/protected/package-private dentro del JAR |
| JAR | packages + recursos | todo lo que es public es visible en el classpath |
| Module | packages | solo los packages exported son visibles para otros módulos |
Un módulo suele empaquetarse como un JAR (un "modular JAR" — un JAR ordinario con un module-info.class en su raíz). La diferencia es el descriptor: coloca el JAR en el classpath y las reglas se ignoran; ponlo en el module path y JPMS las hace cumplir.
Encapsulación fuerte, en una frase
La regla principal: un package es invisible para otros módulos a menos que su módulo lo exports, incluso si sus clases son public. public ahora significa "accesible para el código que puede leer este package", y leer un package requiere un exports junto con un requires. Por eso el propio JDK pudo finalmente ocultar sus internos: java.base exporta java.util pero no jdk.internal.misc.
El JDK también es modular
Desde Java 9, el JDK está dividido en ~70 módulos (java.base, java.sql, java.xml, java.net.http, …). java.base es especial: todo módulo lo requiere implícitamente y contiene los elementos esenciales del lenguaje (java.lang, java.util, java.io). Cada clase que hayas usado alguna vez vive en uno de estos módulos, algo que el ejemplo práctico a continuación hace visible.
Un ejemplo práctico: inspeccionar módulos en tiempo de ejecución
No necesitas escribir un módulo para ver módulos: la API Module en tiempo de ejecución informa el módulo al que pertenece cualquier clase. Este programa pregunta a varias clases a qué módulo pertenecen, comprueba su propio módulo y echa un vistazo a la capa de arranque con la que la JVM inició.
Lo que se desprende de la ejecución:
String,ArrayListyHttpClientinformaronjava.base,java.baseyjava.net.http. Cada clase pertenece exactamente a un módulo, ygetModule()indica cuál — los tipos del lenguaje viven todos enjava.base, mientras queHttpClientestá en su propio módulo, que habría que declarar conrequires java.net.httppara usarlo.- La clase del propio programa informó
isNamed() == falsey ungetName()denull. El código ejecutado desde el classpath cae en el módulo sin nombre, un contenedor de compatibilidad que no requiere nada explícitamente y puede leer todos los demás módulos. Por eso los programas de classpath siguen compilando y ejecutándose sin cambios en Java 9+. ModuleLayer.boot()expuso el grafo de módulos que la JVM resolvió al arrancar — contar losjava.*demuestra que el JDK realmente está dividido en muchos módulos, no en un monolito.java.baseno está abierto (isOpen() == false) pero exporta muchos packages; exponejava.langyjava.utila todos mientras mantiene ocultojdk.internal.*. Exportar un package y abrir un módulo son interruptores distintos — un capítulo posterior vuelve sobreopens.- Nada aquí requirió un
module-info.java. La API Module es metadatos reflexivos disponibles para cualquier programa; escribir tu propio módulo (siguiente capítulo) es lo que te permite declarar estas reglas en lugar de solo observar las del JDK.
Qué cubre el resto de esta parte
module-info.java— las directivas:requires,exports,opens,uses,provides.- Tipos de módulos — módulos con nombre, automáticos y sin nombre, y cómo se combinan.
- Servicios — desacoplar una interfaz de su implementación con
uses/providesyServiceLoader. - Migración — mover una aplicación de classpath existente al module path sin una reescritura completa.
Los módulos son opcionales: una aplicación Java puede ejecutarse indefinidamente en el classpath. Pero comprenderlos explica el JDK moderno, desbloquea los runtimes personalizados con jlink y da a las bibliotecas una encapsulación real. El siguiente capítulo, la declaración module-info.java, escribe el descriptor que convierte a un módulo en módulo.