Apache Maven

Crear un JAR con dependencias con Apache Maven

8 min lectura José Miguel

¿Qué es un JAR con dependencias?

Un requisito típico de los proyectos es agregar la salida junto con sus dependencias, módulos y otros archivos en un solo archivo distribuible.

Un uber-JAR, también conocido como fat-JAR o JAR con dependencias, es un archivo JAR que no solo contiene un programa Java o Scala, sino que también incorpora sus dependencias. Esto significa que el JAR funciona como una distribución «todo en uno» del software.

Cuando distribuimos una aplicación como un JAR simple, quien la ejecute necesita tener todas las dependencias disponibles en el classpath. Con un uber-JAR, en cambio, basta con tener el archivo JAR y una instalación de Java para ejecutar la aplicación. Esto simplifica enormemente el despliegue, especialmente en entornos como clústeres de Spark, servidores de producción o pipelines de CI/CD.

Métodos para construir un uber-JAR

Existen tres métodos comunes para construir un JAR con dependencias:

  • Unshaded: Descomprime todos los archivos JAR y luego vuelve a empaquetarlos en un único JAR. Es el enfoque más sencillo, pero puede causar conflictos si dos dependencias contienen archivos con el mismo nombre (por ejemplo, archivos META-INF/services).
  • JAR de JARs: El archivo JAR final contiene los otros archivos JAR integrados. Requiere un class loader especial para funcionar, lo que puede generar problemas de compatibilidad con ciertos frameworks.
  • Shaded: Igual que Unshaded, pero renombra, es decir, realiza un «shade» de todos los paquetes de todas las dependencias. Este método es el más robusto, ya que evita conflictos de clases al renombrar los paquetes de las dependencias para que no colisionen entre sí.

El plugin Maven Shade

En este artículo vamos a ver el método Shaded. Para ello vamos a emplear el plugin Maven Shade. Este plugin brinda la capacidad de empaquetar el artefacto en un uber-jar, incluidas sus dependencias y cambiar el nombre de los paquetes de algunas de las dependencias.

El plugin Shade tiene un solo goal shade:shade que está vinculado a la fase package y se usa para crear un shaded JAR.

Configuración básica del plugin

Para crear el JAR con dependencias será necesario incluir en el archivo pom.xml del proyecto dentro de la sección del build la siguiente configuración del Maven Shade Plugin:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.6.0</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Una vez que hayamos agregado el plugin a la sección del build podemos personalizar las diferentes configuraciones que deseemos dentro de la etiqueta <configuration>. Para obtener más detalles sobre esta configuración pueden dirigirse a la página oficial de Maven Shade Plugin.

Ejemplo completo de pom.xml

Para tener una visión más clara, veamos un ejemplo completo de un archivo pom.xml que utiliza el Maven Shade Plugin:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ejemplo</groupId>
    <artifactId>mi-aplicacion</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>33.0.0-jre</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.6.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.ejemplo.App</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

En este ejemplo, además de la configuración básica del plugin, hemos añadido un ManifestResourceTransformer. Este transformer se encarga de establecer la clase principal (mainClass) en el archivo MANIFEST.MF del JAR resultante, lo que permite ejecutar el JAR directamente con java -jar.

Ejecutar el empaquetado

Lo que resta ahora es ir desde un terminal a la carpeta del proyecto y ejecutar el comando:

mvn package

Lo que sucederá a continuación es que Maven comenzará a empaquetar nuestro proyecto y creará un JAR con dependencias, que para este caso será un shaded JAR.

Al finalizar el proceso, Maven genera dos archivos en el directorio target/:

  • mi-aplicacion-1.0.0.jar — El uber-JAR (shaded) con todas las dependencias incluidas.
  • original-mi-aplicacion-1.0.0.jar — El JAR original sin dependencias, que Maven renombra automáticamente con el prefijo original-.

Para verificar que el JAR contiene las dependencias, podemos inspeccionar su contenido con el siguiente comando:

jar tf target/mi-aplicacion-1.0.0.jar

Este comando listará todos los archivos empaquetados dentro del JAR. Deberías ver tanto las clases de tu proyecto como las clases de las dependencias (en este ejemplo, las clases de Guava).

Para ejecutar el uber-JAR directamente:

java -jar target/mi-aplicacion-1.0.0.jar

Resource Transformers

Los Resource Transformers son uno de los componentes más poderosos del Maven Shade Plugin. Cuando se combinan múltiples JARs en uno solo, es posible que ciertos archivos de recursos se sobrescriban entre sí. Los transformers permiten manejar estos conflictos de manera inteligente.

Algunos de los transformers más utilizados son:

  • ManifestResourceTransformer — Permite establecer entradas en el archivo MANIFEST.MF, como la clase principal (mainClass).
  • AppendingTransformer — Concatena el contenido de archivos de recursos con el mismo nombre en lugar de sobrescribirlos. Es útil para archivos como META-INF/spring.handlers o META-INF/spring.schemas.
  • ServicesResourceTransformer — Fusiona los archivos META-INF/services de las diferentes dependencias, lo cual es esencial cuando se trabaja con el patrón Service Provider Interface (SPI) de Java.

Un ejemplo de configuración con múltiples transformers:

<configuration>
    <transformers>
        <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>com.ejemplo.App</mainClass>
        </transformer>
        <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
        <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
            <resource>META-INF/spring.handlers</resource>
        </transformer>
    </transformers>
</configuration>

Buenas prácticas

Al trabajar con el Maven Shade Plugin, es recomendable seguir estas buenas prácticas:

  • Excluir dependencias innecesarias: Usa la etiqueta <excludes> dentro de <artifactSet> para evitar incluir dependencias que no sean necesarias en tiempo de ejecución, como librerías de testing (junit, mockito).
  • Usar filtros para eliminar archivos no deseados: Los archivos de firma (*.SF, *.DSA, *.RSA) de los JARs de dependencias pueden causar errores de seguridad. Es recomendable excluirlos:
<configuration>
    <filters>
        <filter>
            <artifact>*:*</artifact>
            <excludes>
                <exclude>META-INF/*.SF</exclude>
                <exclude>META-INF/*.DSA</exclude>
                <exclude>META-INF/*.RSA</exclude>
            </excludes>
        </filter>
    </filters>
</configuration>
  • Definir siempre la mainClass: Si el JAR se va a ejecutar directamente, asegúrate de configurar el ManifestResourceTransformer con la clase principal.
  • Verificar el tamaño del JAR resultante: Un uber-JAR puede crecer significativamente. Revisa que no estés incluyendo dependencias innecesarias.

Errores comunes

Estos son algunos de los errores más frecuentes al trabajar con el Maven Shade Plugin:

  • SecurityException: Invalid signature file digest — Ocurre cuando el JAR contiene archivos de firma de las dependencias. Se soluciona excluyendo los archivos META-INF/*.SF, META-INF/*.DSA y META-INF/*.RSA como se muestra en la sección anterior.
  • No main manifest attribute — Aparece al intentar ejecutar el JAR con java -jar sin haber configurado el ManifestResourceTransformer con la mainClass.
  • Conflictos de clases duplicadas — Cuando dos dependencias incluyen la misma clase con diferente implementación. El shading (renombrado de paquetes) mediante la etiqueta <relocations> puede resolver este problema:
<configuration>
    <relocations>
        <relocation>
            <pattern>com.google.common</pattern>
            <shadedPattern>com.ejemplo.shaded.com.google.common</shadedPattern>
        </relocation>
    </relocations>
</configuration>

Conclusión

Hasta aquí hemos visto cómo poder crear un JAR con dependencias con el plugin Maven Shade Plugin, desde la configuración básica hasta el uso de transformers, filtros y relocations. El Maven Shade Plugin es una herramienta esencial para cualquier proyecto Java o Scala que necesite distribuirse como un único archivo ejecutable.

José Miguel Moya Curbelo
José Miguel Moya Curbelo
Senior Data Engineer & Big Data Instructor

MSc Applied Mathematics · AWS Cloud Practitioner · SCRUM Master. Especializado en arquitecturas de datos de alto rendimiento con Apache Spark, Snowflake, Python y Scala.

Conectar en LinkedIn

Artículos Relacionados

Deja un comentario

Tu dirección de correo electrónico no será publicada.