40

I have a Maven project and I want to create two executable jar files from it. One will be used interactively by users and a second will be run as a scheduled job that reads the log files produced by the former. In the end, I would expect the two jar files to be identical except for the Main-Class attribute in the MANIFEST.MF file.

I am using maven-antrun-plugin to create an executable jar and this seemed to be working fine until I tried to create a second jar file by introducing Maven profiles. The relevant section of my POM file looks like this:

<profiles>
    <profile>
        <id>publisher</id>
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                ...
                <plugin>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.4</version>
                    <configuration>
                        <appendAssemblyId>false</appendAssemblyId>
                        <finalName>${project.artifactId}</finalName>
                        <archive>
                            <manifest>
                                <mainClass>fully.qualified.path.Publisher</mainClass>
                            </manifest>
                        </archive>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                    </configuration>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
    <profile>
        <id>logReader</id>
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                ...
                <plugin>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.4</version>
                    <configuration>
                        <appendAssemblyId>false</appendAssemblyId>
                        <finalName>${project.artifactId}-logReader</finalName>
                        <archive>
                            <manifest>
                                <mainClass>fully.qualified.path.LogReader</mainClass>
                            </manifest>
                        </archive>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                    </configuration>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Really, the only difference between the two is the "finalName" and "mainClass" elements as defined within the plugin.

When I try to execute mvn:package on both profiles (I'm using IntelliJ IDEA, by the way), I get two .jar files, but one is correct and the other is incorrect. The "correct" one contains all the dependencies and a valid MANIFEST.MF file. The "incorrect" one contains no dependencies and the MANIFEST.MF file lacks the "Main-Class" property that I need in order for it to be executable.

I have found that if I ever run just one profile or the other, that it works fine but, if I try to execute both profiles at once, it fails. I also get the following notes in my log, but I must admit that I don't completely understand what they are saying:

[INFO] Building jar: .../target/publisher.jar
...
[INFO] Building jar: .../target/publisher-logReader.jar
[WARNING] Configuration options: 'appendAssemblyId' is set to false, and 'classifier' is missing.
Instead of attaching the assembly file: .../target/publisher-logReader.jar, it will become the file for main project artifact.
NOTE: If multiple descriptors or descriptor-formats are provided for this project, the value of this file will be non-deterministic!
[WARNING] Replacing pre-existing project main-artifact file: .../target/publisher.jar with assembly file: .../target/publisher-logReader.jar

Any thoughts about this? Is it possible to produce two jar files with a single mvn:package in this way, or am I barking up the wrong tree?

Thanks!

McGlone
  • 3,434
  • 5
  • 26
  • 31

2 Answers2

58

So as soon as I posted this, I found this thread:

Create multiple runnable Jars (with depencies included) from a single Maven project

This uses a different approach in that it doesn't use two profiles, it uses two executions, as such:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.4</version>
    <executions>
        <execution>
            <id>build-publisher</id>
            <configuration>
                <appendAssemblyId>false</appendAssemblyId>
                <archive>
                    <manifest>
                        <mainClass>fully.qualified.path.Publisher</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <finalName>${project.artifactId}</finalName>
            </configuration>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
        <execution>
            <id>build-logReader</id>
            <configuration>
                <appendAssemblyId>false</appendAssemblyId>
                <archive>
                    <manifest>
                        <mainClass>fully.qualified.path.LogReader</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <finalName>${project.artifactId}-logReader</finalName>
            </configuration>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

This seems to be working. The moral of the story seems to be that I don't completely understand profiles or when they should be used.

Community
  • 1
  • 1
McGlone
  • 3,434
  • 5
  • 26
  • 31
  • 2
    this solution works better (at least for me) compared to [this one](http://stackoverflow.com/a/8726969/288875) which lacks the `` and `` elements. – Andre Holzner Jun 25 '13 at 12:25
0

When I was searching for different ways to create multiple jar's from the same project, I came across several answers, and it turned out that other answers were marked as duplicates of that particular answer. Additionally, a quick internet search also led me to a that question, which was not as comprehensive as it could be (in my opinion). I noticed that in many cases, people suggest using the maven-assembly-plugin, maven-shade-plugin, and sometimes the onejar-maven-plugin to create multiple jars. However, there is one more way to achieve this by using the simple maven-jar-plugin, which I'll describe in detail below.


All possible solutions

Here is a short description of the plugins that you can use to assemble your program into several jar archives (I obtained most of the information directly from the official sites):

maven-assembly-plugin

The Assembly Plugin for Maven enables developers to combine project output into a single distributable archive that also contains dependencies, modules, site documentation, and other files. Currently it can create distributions in the following formats: zip, tar, tar.gz, tar.bz2, tar.snappy, tar.xz, tar.zst, jar, dir, war. If your project wants to package your artifact in an uber-jar, the assembly plugin provides only basic support. For more control, use the Maven Shade Plugin. You can read more about maven-assembly-plugin right here.

maven-shade-plugin

This plugin provides the capability to package the artifact in an uber-jar, including its dependencies and to shade - i.e. rename - the packages of some of the dependencies. You can read more about shading here and about the plugin itself righthere.

maven-jar-plugin

This plugin provides the capability to build jars. That's its primary purpose, and it doesn't offer any additional functionality beyond that. You can read more about maven-jar-plugin right here.

onejar-maven-plugin

This is an unofficial plugin that enables you to build an executable jar with Maven 2, including all dependencies. Here is a helpful answer that explains how you can use it. However, it's important to note that the latest version of the plugin was released on December 13, 2011, which means that it might be outdated.

In conclusion, the maven-assembly-plugin is a widely used plugin with extensive settings for adjusting project aspects. For greater control in creating fat jar's with dependencies and package shading for multiple library versions, the maven-shade-plugin is recommended. The maven-jar-plugin serves as a lightweight and efficient solution for building multiple jars without additional functionality, making it suitable for many cases. However, it's best to avoid using the outdated onejar-maven-plugin.


The usage of maven-jar-plugin

The usage of the maven-assembly-plugin and maven-shade-plugin was well described in that thread and in this question. Here, I will provide some examples of how to create multiple jars by using the simple and lightweight maven-jar-plugin. For example, you can define it as follows:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <version>3.3.0</version>
  <configuration>
    <finalName>
      my-application
    </finalName>
  </configuration>
  <executions>
    <execution>
      <id>production</id>
      <phase>package</phase>
      <goals>
        <goal>jar</goal>
      </goals>
      <configuration>
        <classifier>production</classifier>
        <archive>
          <manifest>
            <mainClass>
              org.example.Production
            </mainClass>
          </manifest>
        </archive>
      </configuration>
    </execution>
    <execution>
      <id>staging</id>
      <phase>package</phase>
      <goals>
        <goal>jar</goal>
      </goals>
      <configuration>
        <classifier>staging</classifier>
        <archive>
          <manifest>
            <mainClass>
              org.example.Staging
            </mainClass>
          </manifest>
        </archive>
      </configuration>
    </execution>
    <execution>
      <id>testing</id>
      <phase>package</phase>
      <goals>
        <goal>jar</goal>
      </goals>
      <configuration>
        <classifier>testing</classifier>
        <archive>
          <manifest>
            <mainClass>
              org.example.Testing
            </mainClass>
          </manifest>
        </archive>
      </configuration>
    </execution>
  </executions>
</plugin>

After executing the mvn package command, you will find three different jar's in the target directory, each of which has its own main class:

  • my-application-production.jar - the main class is org.example.Production
  • my-application-staging.jar - the main class is org.example.Staging
  • my-application-testing.jar - the main class is org.example.Testing

Hence, when you run java -jar target/my-application-staging.jar, the org.example.Staging class will be invoked. However, there is one trick - the maven-jar-plugin only packs your classes into a jar archive without adding the dependencies. If you need to include dependencies in the jar, such as when creating an executable jar, you will likely need to use the maven-dependency-plugin in conjunction with the maven-jar-plugin:

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

Then, in maven-jar-plugin you will have to specify the classpath properties:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jar-plugin</artifactId>
  <version>3.3.0</version>
  <configuration>
    <finalName>
      my-application
    </finalName>
  </configuration>
  <executions>
    <execution>
      <id>production</id>
      <phase>package</phase>
      <goals>
        <goal>jar</goal>
      </goals>
      <configuration>
        <classifier>production</classifier>
        <archive>
          <manifest>
            <mainClass>
              org.example.Production
            </mainClass>
            <addClasspath>true</addClasspath>
            <classpathPrefix>deps/</classpathPrefix>
          </manifest>
        </archive>
      </configuration>
    </execution>
  </executions>
</plugin>

Please note that we have added the addClasspath and classpathPrefix properties to the maven-jar-plugin. It is important to ensure that the name of the directory set in the outputDirectory of the maven-dependency-plugin matches the name specified in the classpathPrefix property of the maven-jar-plugin

That's all. I hope it will be helpful to someone.

Volodya Lombrozo
  • 2,325
  • 2
  • 16
  • 34