Módulos en Java

Por Andrés González

En la versión 9 de Java, se introdujo una actualización sobre gestión de aplicaciones, el sistema de módulos. Esta nueva organización permite modularizar una aplicación, facilitando su encapsulación, mantenibilidad y reutilización de código.

En este artículo, exploraremos en profundidad el concepto de módulos en Java, cómo se definen, sus beneficios, y cómo pueden transformar la forma en que diseñamos aplicaciones escalables y eficientes.

¡Descubramos cómo funciona!

¿Qué es un módulo?

Un módulo es un conjunto de paquetes, clases e interfaces que define la visibilidad y las dependencias con otros módulos.

¿Cómo era Java antes del sistema de módulos?

Antes de Java 9, el sistema de organización del código dependía del classpath, que es un conjunto de rutas donde la JVM busca clases y librerías.

Tampoco existía una forma explícita de encapsular paquetes de manera estricta.

Tenía una forma similar a esta:

forma explícita de encapsular paquetes de manera estricta

Lo que causaba algunos problemas como:

  • Conflicto de nombres entre distintas bibliotecas al poder posicionarlas en el mismo paquete y causando colisión en tiempo de ejecución.
  • Acceso a clases de forma no intencional, ya que al estar en el mismo paquete aunque fuera de otra librería, Java lo considera en el mismo directorio y no nos permite encapsularlas.
  • Dependencias transitivas entre versiones de librerías no compatibles, lo que hace que provoquen errores en tiempo de ejecución.

Cómo soluciona ésto Java 9

Con el sistema de módulos, Java 9, la organización y visibilidad de dependencias pasó a tener una una forma parecida a esta:

la organización y visibilidad de dependencias pasó a tener una una forma parecida a esta:

En este sistema, se introdujo un descriptor de módulos llamado module-info.java, que  contiene la metainformación sobre el módulo, como:

  • El nombre del módulo
  • Los paquetes expuestos
  • La dependencias con otros módulos
  • Los servicios consumidos e implementados.

Con esto, se pretende mejorar la gestión y encapsulación del código.

Se pueden generar varios module-info.java por cada módulo que queramos crear en el propio paquete o uno a nivel de raíz del proyecto dentro de la carpeta src.

Ejemplo:

Se pueden generar varios module-info.java por cada módulo que queramos crear en el propio paquete o uno a nivel de raíz del proyecto dentro de la carpeta src.

Se pueden generar varios module-info.java por cada módulo que queramos crear en el propio paquete o uno a nivel de raíz del proyecto dentro de la carpeta src.

¿Qué ventajas ofrece este sistema?

  • Mejor control de acceso. Permite ocultar implementaciones internas dejando que sólo ciertas partes sean utilizadas por otras aplicaciones.
  • Claridad en las dependencias. A través de module-info, se especifica claramente las dependencias entre módulos, que son evaluadas al compilar y al lanzar la aplicación.
  • Paquetes de distribución más pequeños. Facilita crear aplicaciones ligeras y mejorar el rendimiento.
  • Existencia de paquetes únicos. No puede haber dos módulos que expongan el mismo paquete.

Vale, ¿pero qué desventajas tiene?

  • Añade complejidad ya que requiere definir cada module-info.java y gestionar dependencias manualmente.
  • Puede hacer que librerías no modulares requieran de configuraciones extra.

Compatibilidad con código legacy

Ahora la pregunta clave es, ¿funciona el código generado antes del uso de módulos? 

Pues la respuesta es sí. Cuando se ejecuta código sin module-info.java, Java lo trata como un módulo anónimo dentro de algo llamado unnamed module. Éste actúa como contenedor para código antiguo, para que se pueda utilizar con los módulos y funcione sin tener que realizar ninguna modificación.

Tipos de módulos

En el sistema de módulos podemos distinguir 3 tipos:

Módulos estándar

Son los conjuntos de paquetes definidos con module-info.java

Módulos automáticos

Son los paquetes que no forman parte de un módulo. Desde las clases de este módulo, se puede acceder a cualquier paquete que se encuentre en el classpath, incluyendo módulos anónimos como estándares.

A estos módulos sólo pueden acceder otros módulos anónimos.

Módulos anónimos

Son aquellos módulos anónimos que se incluyen como parte del module-path. Cualquier tipo de módulo puede acceder a ellos. 

A continuación dejamos un gráfico ilustrando la relación entre los tipos de módulos.

Relacionales entre tipo de módulos

¿Cómo construímos un módulo?

Un módulo debe tener un nombre que sea un identificador válido en Java o un nombre válido de paquete. Este debe ser unívoco y no es recomendable que no sea el mismo que el de una clase, interfaz o paquete para evitar confusiones.

Un módulo se puede definir con las siguientes palabras reservadas:

Palabras clave

¿Cómo construímos un módulo?

Compilación

Una vez definido toda la configuración del módulo lo podremos compilar con el siguiente comando:

javac –module-path [ruta_dir_modulo_dep] -d [ruta_dir_destino] [ficheros]
    • Con el comando javac, ejecutamos el compilador de Java.
    • Con la opción –module-path [ruta_dir_modulo_dep], se define donde se encuentran los módulos de los que depende el módulo que estamos compilando.
    • Con la opción –d [ruta_dir_destino], se especifica el directorio donde se guardarán los archivos compilados. (Los ficheros .class)
    • El valor ficheros, define la lista de archivos .java que se van a compilar. Se puede utilizar metacaracteres para ésta tarea.

    Usemos como ejemplo de compilación la estructura que propusimos al principio de todo:

    Compilación

    Una vez hecho esto, hay 2 formas de empaquetar los módulos y cada uno se ejecuta de forma diferente.

    Empaquetar módulos con .jar

    Esta es la forma más habitual de crear un módulo.

    Después de compilar, podemos generar el .jar con el siguiente comando:

    jar -c –file=[dir_destino/nombre_archivo.jar] –C [path_modulo] .

    • Con el comando jar,  ejecutamos la herramienta para manipular archivos .jar.
    • Con la opción -c, creamos el .jar.
    • Con la opción –file= [dir_destino/nombre_archivo.jar], especificamos la ubicación y el nombre del .jar que generaremos.
    • Con la opción -C path_modulo ., cambiamos al directorio que especificamos dentro de path_modulo, que será el sitio donde hemos dejado los ficheros compilados y el punto (.) que aparece, indica que todos los archivos indicados en el path_modulo se incluirán en el .jar.

    Ejemplo:

    Empaquetar módulos con .jar

    Ejecución

    Para ejecutar los módulos en caso de que tengan una clase principal, utilizaremos el siguiente comando:

    java –module-path  [ruta_dir_modulo] –module [nombre_modulo/ruta_clase]
      • Con el comando java, ejecutaremos la JVM.
      • Con la opción –module-path [ruta_dir_modulo], se define donde se encuentran los módulos compilados, separados por coma.
      • Con la opción –module [nombre_modulo/ruta_clase], se especifica el módulo que contiene la clase principal y la ruta cualificada de la clase que tiene el método main(String [] args) para ejecutar.

      Ejecución

      Empaquetación de módulos con .jmod

      Otra forma de construir y empaquetar módulos es con los archivos .jmod que introdujo Java 9. 

      A diferencia de la forma anterior de construcción de módulos, realizarlos de esta forma nos permite introducir archivos nativos, configuración y documentación y otros elementos que no se pueden incluir en un JAR estándar.

      Pero la principal desventaja que tienen, es que no son portables ya que al poder contener archivos nativos del propio sistema operativo. Además se necesitan herramientas específicas de java para ejecutarlos.

      La instrucción para empaquetar los módulos de esta forma es la siguiente:

      jmod create –class-path [path_modulo] [fichero.jmod]

      • Con el comando jmod, se llama a la herramienta que maneja archivos .jmod.
      • Con la opción create, creamos el fichero .jmod
      • Con la opción –class-path [path_modulo] [fichero.jmod], especificamos el directorio donde están los archivos compilados y el nombre que tendrá nuestro .jmod

      Ejemplo:

      Empaquetación de módulos con .jmod

      Ejecución

      Los ficheros .jmod no se pueden ejecutar de forma directa, de modo que tenemos convertirlos en una imagen del runtime con jlink.

      El formato de la instrucción para crear la imagen es la siguiente:

      jlink –module-path [dir] –add-modules [name, name2] –output [path]

      • Con el comando jlink, llamamos a la herramienta para crear una imagen optimizada.
      • Con la opción –module-path [dir], se define la ubicación donde se encuentran los archivos .jmod y/o .jar.
      • Con la opción –add-modules [name, name2], listamos los módulos a incluir en la imagen.
      • Con la opción –output [path], decimos dónde se va a generar la imagen.

      Ejemplo:

      Los ficheros .jmod no se pueden ejecutar de forma directa, de modo que tenemos convertirlos en una imagen del runtime con jlink.

      Después de que se genere, podremos ejecutarlo.

      Conclusión

      En definitiva, ahora tenemos una visión general de esta funcionalidad para poder aprovecharla en nuestros proyectos en caso de que sea necesario.

       



      <span style="font-size:80%">Autor </span><a href="https://blog.kairosds.com/author/andresgonzalez/" target="_self">Andrés González</a>

      Autor Andrés González

      Jun 3, 2025

      Otros artículos

      ¿Por qué es crucial utilizar Locale?

      ¿Por qué es crucial utilizar Locale?

      Por Andrés González Introducción En los últimos años me he enfrentado a proyectos en los cuales se necesitaban que las aplicaciones fueran multi idioma, lo cual hacía fundamental realizar un buen tratamiento de los datos que los usuarios introducían. Por eso, voy...