Jar Ejecutable con Maven

Los archivos .jar son empaquetados de clases de Java, se podría comparar con un simple archivo .zip que contiene todos los .class de nuestra aplicación. En el pueden o no haber una o varias clases principales.

Las clases principales son las que contienen un método con la firma public static void main(String[] args), por ejemplo:

package com.example.executable;
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

La manera tradicional de ejecutar esta clase principal es:

$ java -cp example.jar com.example.executable.Main
Hello World

Si damos doble clic o ejecutamos un java -jar nombre-archivo-jar obtendremos un error:

$ java -jar example.jar
no hay ningún atributo de manifiesto principal en example.jar

Para poder ejecutarlo con doble clic o java -jar nombre-archivo-jar se debe incluir dentro del JAR el archivo de manifiesto, localizado en META-INF/MANIFEST.MF y en su contenido el atributo Main-Class indicar el nombre de la clase principal que se debe ejecutar.

NOTA: El archivo META-INF/MANIFEST.MF tiene que terminar con una línea en blanco para que sea correctamente interpretado por Java.

Adicionalmente, el proyecto puede requerir dependencias de terceros, estas dependencias se debe agregar también en el archivo de manifiesto, en el contenido del atributo Class-Path con la ruta de los JARs requeridos, separando cada uno con espacio.

Por ejemplo, si agregamos dependencias a SLF4J con Log4j en el pom.xml

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.30</version>
</dependency>

Modificamos la clase principal com.example.executable.Main:

package com.example.executable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
    private static final Logger LOG = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
        LOG.info("Hello World");
    }
}

Y luego modificamos el archivo META-INF/MANIFEST.MF:

Manifest-Version: 1.0
Class-Path: libs/slf4j-log4j12-1.7.30.jar libs/slf4j-api-1.7.30.jar libs/log4j-1.2.17.jar
Main-Class: com.example.executable.Main

Se deben copiar todas las dependencias en una carpeta llamada libs junto al archivo JAR.

Ya con esto podemos ejecutar la aplicación dando doble clic en ella o ejecutando java -jar nombre-archivo-jar sin problemas.

Copiando Dependencias

Maven cuenta con plugins que nos facilitan la tarea de crear el archivo de manifiesto y copiar las dependencias.

Con maven-jar-plugin podemos indicar la clase principal en el atributo mainClass y el nombre de la carpeta de dependencias en el atributo classpathPrefix.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <archive>
            <manifest>
                <addClasspath>true</addClasspath>
                <classpathPrefix>libs/</classpathPrefix>
                <mainClass>com.example.executable.Main</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>

Por su parte, el plugin maven-dependency-plugin nos ayuda a copiar todas las dependencias del proyecto en la carpeta indicada.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>
                    ${project.build.directory}/libs
                </outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

Generar un FatJar con Plugin

Un FatJar (también conocido como UberJar) es un archivo JAR que contiene no solo sus clases, sino que también contiene dentro de él mismo todas sus dependencias. Su ventaja consiste en ser un único archivo (.jar) para distribuir la aplicación (no se requiere copiar la carpeta libs como en el caso anterior).

Existen varios plugins en Maven que nos permiten generar un FatJar, los siguientes son los más populares.

Apache Maven Assembly Plugin

Se debe agregar el siguiente plugin al proyecto:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.3.0</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>com.example.executable.Main</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
        </execution>
    </executions>
</plugin>

Apache Maven Shade Plugin

Se debe agregar el siguiente plugin al proyecto:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.2.4</version>
    <executions>
        <execution>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <shadedArtifactAttached>true</shadedArtifactAttached>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>com.example.executable.Main</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

Onejar Maven Plugin

Se debe agregar el siguiente plugin al proyecto:

<plugin>
    <groupId>com.jolira</groupId>
    <artifactId>onejar-maven-plugin</artifactId>
    <version>1.4.4</version>
    <executions>
        <execution>
            <goals>
                <goal>one-jar</goal>
            </goals>
            <configuration>
                <mainClass>com.example.executable.Main</mainClass>
                <attachToBuild>true</attachToBuild>
                <filename>${project.build.finalName}-one.${project.packaging}</filename>
            </configuration>
        </execution>
    </executions>
</plugin>

Código Fuente de Ejemplo

📦 executable-jar-maven

Documentación de los Plugins

Referencia