1. ¿Qué es module-info.java y dónde se encuentra?
Un módulo en Java siempre empieza con el archivo module-info.java. Es una especie de «pasaporte» del módulo, donde declaras su nombre, los paquetes que exporta y las dependencias de otros módulos.
¿Dónde buscar?
El archivo module-info.java debe estar en la raíz de la carpeta de fuentes de tu módulo. Por ejemplo, si tienes la siguiente estructura de proyecto:
project-root/
└── src/
└── my.module.name/
├── module-info.java
└── com/
└── example/
└── api/
└── MyClass.java
Aquí my.module.name es el nombre del módulo (hablaremos de las reglas de nomenclatura un poco más adelante).
¡Sin este archivo, el módulo simplemente no se considera un módulo!
Si no está, es un «viejo» proyecto de Java, aunque tenga un montón de carpetas y clases.
2. Sintaxis de module-info.java: elementos principales
Veamos enseguida un ejemplo del archivo más simple; no es nada del otro mundo, de verdad.
module my.module.name {
exports com.example.api;
requires java.sql;
}
Desgranémoslo por partes:
module my.module.name { ... }
Esta es la declaración de un módulo con el nombre my.module.name. El nombre del módulo es un identificador único, que normalmente coincide con el paquete raíz (por ejemplo, com.example.app).
Dato curioso: si llamas a tu módulo java.base, al compilador no le hará mucha gracia. No intentes suplantar módulos estándar.
exports com.example.api;
Esta línea dice: «Yo, módulo, estoy dispuesto a compartir todo lo que hay en el paquete com.example.api». Todo lo que haya dentro de ese paquete y esté marcado como public será visible para otros módulos. El resto queda estrictamente para uso interno.
requires java.sql;
Y aquí confesamos al compilador: «Para funcionar, necesito el módulo estándar java.sql». Sin ello, el compilador no nos permitirá usar clases de ese módulo.
Palabras clave adicionales (para cultura general)
- opens <package>; — abre el paquete para la reflexión (por ejemplo, para bibliotecas de serialización como Jackson).
- uses <service-interface>; — indica que el módulo utiliza un servicio (interfaz).
- provides <service-interface> with <implementation-class>; — indica que el módulo proporciona una implementación del servicio.
En esta lección nos centraremos en exports y requires: son necesarios en la gran mayoría de proyectos de aprendizaje y en producción.
3. Ejemplos de module-info.java
Ejemplo 1. Módulo mínimo
module com.example.hello {
exports com.example.hello.api;
}
- Este módulo exporta únicamente el paquete com.example.hello.api.
- Todo lo que esté, por ejemplo, en com.example.hello.internal quedará oculto para otros módulos, aunque haya clases public.
Ejemplo 2. Módulo con dependencia
module com.example.dbclient {
exports com.example.db.api;
requires java.sql;
}
Podemos utilizar JDBC, pero solo porque hemos declarado honestamente la dependencia de java.sql.
Ejemplo 3. Varios paquetes exportados
module com.example.library {
exports com.example.library.api;
exports com.example.library.utils;
}
Puedes exportar tantos paquetes como quieras (pero no conviene exportarlo todo sin criterio: ahí está el sentido de los módulos).
4. Limitaciones y reglas
Nombre del módulo
- Suele coincidir con el paquete raíz (por ejemplo, com.example.app).
- No debe coincidir con los nombres de los módulos estándar (java.base, java.sql, etc.).
- No debe contener espacios, caracteres especiales ni empezar por un dígito, etc.
- Recomendación: usa el dominio invertido de tu organización o proyecto para evitar conflictos.
Un módulo — un solo module-info.java
En un módulo solo puede haber un archivo de este tipo. Si hay dos, el compilador te montará un «escándalo modular».
Un paquete se exporta solo desde un único módulo
Un mismo paquete no puede exportarse desde dos módulos distintos. Es como tener dos pasaportes con el mismo nombre: el estado no lo aprobará.
Paquetes dentro del módulo
Solo puedes exportar los paquetes que existen realmente en la estructura de fuentes de ese módulo. Exportar un paquete inexistente provocará un error de compilación.
5. Práctica: creamos module-info.java en el proyecto
Supongamos que tenemos un proyecto sencillo con el siguiente contenido:
project-root/
└── src/
└── com.example.greetings/
├── module-info.java
└── com/
└── example/
└── greetings/
├── api/
│ └── Greeter.java
└── internal/
└── SecretSauce.java
Paso 1. Creamos module-info.java
module com.example.greetings {
exports com.example.greetings.api;
}
Paso 2. Intentamos usar una clase del paquete internal en otro módulo
Supongamos que tenemos un segundo módulo com.example.app, que quiere acceder a SecretSauce:
module com.example.app {
requires com.example.greetings;
}
import com.example.greetings.internal.SecretSauce; // ERROR!
Resultado:
El compilador dirá: «El paquete com.example.greetings.internal no está exportado por el módulo com.example.greetings». Incluso si la clase SecretSauce es public, no está disponible para otros módulos.
¡Esto es la verdadera encapsulación a nivel de módulos!
Paso 3. Probamos a no declarar requires
Si en com.example.app no escribimos requires com.example.greetings;, y aun así intentamos usar una clase de com.example.greetings.api, el compilador dará un error:
package com.example.greetings.api is not visible
6. Errores típicos al trabajar con module-info.java
Error n.º 1: discordancia entre el nombre del módulo y la estructura del proyecto.
Si llamas al módulo com.example.app pero la estructura de carpetas es src/main/java/app, el compilador no entenderá qué quieres. El nombre del módulo suele coincidir con el paquete raíz y las carpetas deben reflejarlo.
Error n.º 2: exportarlo todo sin discernimiento.
Debes exportar únicamente lo que de verdad deba ser visible para otros módulos. No hagas exports com.example; solo «porque es más fácil». Eso rompe la encapsulación.
Error n.º 3: olvidar añadir requires.
Si usas clases de otro módulo o de la biblioteca estándar (por ejemplo, java.sql) pero olvidas declarar la dependencia, habrá un error de compilación.
Error n.º 4: paquete inexistente en exports.
Si escribes exports com.example.foo; y tal paquete no existe, el compilador dirá que estás «exportando aire».
Error n.º 5: clases public en un paquete no exportado.
Si una clase está declarada como public pero reside en un paquete que no se exporta, esa clase solo será visible dentro del módulo. No es un error, pero a menudo sorprende a los principiantes.
Error n.º 6: varios module-info.java en un mismo módulo.
En un módulo debe haber un único archivo module-info.java. Si hay dos, el compilador no podrá construir el proyecto.
GO TO FULL VERSION