Maven: The Complete Reference
8.2. Assembly Basics

Before we go any further, it’s best to take a minute and talk about
the two main goals in the Assembly plugin: assembly:assembly, and
the single mojo. I list these two goals in different ways because it
reflects the difference in how they’re used. The assembly:assembly
goal is designed to be invoked directly from the command line, and
should never be bound to a build lifecycle phase. In contrast, the
single mojo is designed to be a part of your everyday build, and
should be bound to a phase in your project’s build lifecycle.
The main reason for this difference is that the assembly:assembly
goal is what Maven terms an aggregator mojo; that is, a mojo which is
designed to run at most once in a build, regardless of how many
projects are being built. It draws its configuration from the root
project - usually the top-level POM or the command line. When bound to
a lifecycle, an aggregator mojo can have some nasty side-effects. It
can force the execution of the package lifecycle phase to execute
ahead of time, and can result in builds which end up executing the
package phase twice.
Because the assembly:assembly goal is an aggregator mojo, it raises
some issues in multi-module Maven builds, and it should only be called
as a stand-alone mojo from the command-line. Never bind an
assembly:assembly execution to a lifecycle
phase. assembly:assembly was the original goal in the Assembly
plugin, and was never designed to be part of the standard build
process for a project. As it became clear that assembly archives were
a legitimate requirement for projects to produce, the single mojo
was developed. This mojo assumes that it has been bound to the correct
part of the build process, so that it will have access to the project
files and artifacts it needs to execute within the lifecycle of a
large multi-module Maven project. In a multi-module environment, it
will execute as many times as it is bound to the different module
POMs. Unlike assembly:assembly, single will never force the
execution of another lifecycle phase ahead of itself.
The Assembly plugin provides several other goals in addition to these
two. However, discussion of these other mojos is beyond the scope of
this chapter, because they serve exotic or obsolete use cases, and
because they are almost never needed. Whenever possible, you should
definitely stick to using assembly:assembly for assemblies generated
from the command line, and to single for assemblies bound to
lifecycle phases.
While many people opt to create their own archive recipes - called assembly descriptors - this isn’t strictly necessary. The Assembly plugin provides built-in descriptors for several common archive types that you can use immediately without writing a line of configuration. The following assembly descriptors are predefined in the Maven Assembly plugin:
-
bin -
The
bindescriptor is used to bundle project LICENSE, README, and NOTICE files with the project’s main artifact, assuming this project builds a jar as its main artifact. Think of this as the smallest possible binary distribution for completely self-contained projects. -
jar-with-dependencies -
The
jar-with-dependenciesdescriptor builds a JAR archive with the contents of the main project jar along with the unpacked contents of all the project’s runtime dependencies. Coupled with an appropriateMain-ClassManifest entry (discussed in “Plugin Configuration” below), this descriptor can produce a self-contained, executable jar for your project, even if the project has dependencies. -
project -
The
projectdescriptor simply archives the project directory structure as it exists in your file-system and, most likely, in your version control system. Of course, the target directory is omitted, as are any version-control metadata files like the CVS and .svn directories we’re all used to seeing. Basically, the point of this descriptor is to create a project archive that, when unpacked, can be built using Maven. -
src -
The
srcdescriptor produces an archive of your project source and pom.xml files, along with any LICENSE, README, and NOTICE files that are in the project’s root directory. This precursor to the project descriptor produces an archive that can be built by Maven in most cases. However, because of its assumption that all source files and resources reside in the standard src directory, it has the potential to leave out non-standard directories and files that are nonetheless critical to some builds.
The Assembly plugin can be executed in two ways: you can invoke it directly from the command line, or you can configure it as part of your standard build process by binding it to a phase of your project’s build lifecycle. Direct invocation has its uses, particularly for one-off assemblies that are not considered part of your project’s core deliverables. In most cases, you’ll probably want to generate the assemblies for your project as part of its standard build process. Doing this has the effect of including your custom assemblies whenever the project is installed or deployed into Maven’s repositories, so they are always available to your users.
As an example of the direct invocation of the Assembly plugin, imagine
that you wanted to ship off a copy of your project which people could
build from source. Instead of just deploying the end-product of the
build, you wanted to include the source as well. You won’t need to do
this often, so it doesn’t make sense to add the configuration to your
POM. Instead, you can use the following command:
$ mvn -DdescriptorId=project assembly:single ... [INFO] [assembly:single] [INFO] Building tar : /Users/~/mvn-examples-1.0/assemblies/direct-invocation/\ target/direct-invocation-1.0-SNAPSHOT-project.tar.gz [INFO] Building tar : /Users/~/mvn-examples-1.0/assemblies/direct-invocation/\ target/direct-invocation-1.0-SNAPSHOT-project.tar.bz2 [INFO] Building zip: /Users/~/mvn-examples-1.0/assemblies/direct-invocation/\ target/direct-invocation-1.0-SNAPSHOT-project.zip ...
Imagine you want to produce an executable JAR from your project. If your project is totally self-contained with no dependencies, this can be achieved with the main project artifact using the archive configuration of the JAR plugin. However, most projects have dependencies, and those dependencies must be incorporated in any executable JAR. In this case, you want to make sure that every time the main project JAR is installed or deployed, your executable JAR goes along with it.
Assuming the main class for the project is
org.sonatype.mavenbook.App, the following POM configuration will
create an executable JAR:
Assembly Descriptor for Executable JAR.
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook.assemblies</groupId>
<artifactId>executable-jar</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Assemblies Executable Jar Example</name>
<url>http://sonatype.com/book</url>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-2</version>
<executions>
<execution>
<id>create-executable-jar</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>
jar-with-dependencies
</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.sonatype.mavenbook.App</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
There are two things to notice about the configuration above. First,
we’re using the descriptorRefs configuration section instead of the
descriptorId parameter we used last time. This allows multiple
assembly types to be built from the same Assembly plugin execution,
while still supporting our use case with relatively little extra
configuration. Second, the archive element under configuration
sets the Main-Class manifest attribute in the generated JAR. This
section is commonly available in plugins that create JAR files, such
as the JAR plugin used for the default project package type.
Now, you can produce the executable JAR simply by executing mvn
package. Afterward, we’ll also get a directory listing for the target
directory, just to verify that the executable JAR was
generated. Finally, just to prove that we actually do have an
executable JAR, we’ll try executing it:
$ mvn package
... (output omitted) ...
[INFO] [jar:jar]
[INFO] Building jar: ~/mvn-examples-1.0/assemblies/executable-jar/target/\
executable-jar-1.0-SNAPSHOT.jar
[INFO] [assembly:single {execution: create-executable-jar}]
[INFO] Processing DependencySet (output=)
[INFO] Building jar: ~/mvn-examples-1.0/assemblies/executable-jar/target/\
executable-jar-1.0-SNAPSHOT-jar-with-dependencies.jar
... (output omitted) ...
$ ls -1 target
... (output omitted) ...
executable-jar-1.0-SNAPSHOT-jar-with-dependencies.jar
executable-jar-1.0-SNAPSHOT.jar
... (output omitted) ...
$ java -jar \
target/executable-jar-1.0-SNAPSHOT-jar-with-dependencies.jar
Hello, World!
From the output shown above, you can see that the normal project build
now produces a new artifact in addition to the main JAR file. The new
one has a classifier of jar-with-dependencies. Finally, we verified
that the new JAR actually is executable, and that executing the JAR
produced the desired output of “Hello, World!”
When you generate assemblies as part of your normal build process,
those assembly archives will be attached to your main project’s
artifact. This means they will be installed and deployed alongside the
main artifact, and are then resolvable in much the same way. Each
assembly artifact is given the same basic coordinates (groupId,
artifactId, and version) as the main project. However, these
artifacts are attachments, which in Maven means they are derivative
works based on some aspect of the main project build. To provide a
couple of examples, source assemblies contain the raw inputs for the
project build, and jar-with-dependencies assemblies contain the
project’s classes plus its dependencies. Attached artifacts are
allowed to circumvent the Maven requirement of one project, one
artifact precisely because of this derivative quality.
Since assemblies are (normally) attached artifacts, each must have a
classifier to distinguish it from the main artifact, in addition to
the normal artifact coordinates. By default, the classifier is the
same as the assembly descriptor’s identifier. When using the built-in
assembly descriptors, as above, the assembly descriptor’s identifier
is generally also the same as the identifier used in the
descriptorRef for that type of assembly.
Once you’ve deployed an assembly alongside your main project artifact,
how can you use that assembly as a dependency in another project? The
answer is fairly straightforward. Projects depend on other projects
using a combination of four basic elements, referred to as a project’s
coordinates: groupId, artifactId, version, and packaging. In
Section 5.5.3, “Platform Classifiers”, multiple platform-specific
variants of a project’s artifact are available, and the project
specifies a classifier element with a value of either win or
linux to select the appropriate dependency artifact for the target
platform. Assembly artifacts can be used as dependencies using the
required coordinates of a project plus the classifier under which the
assembly was installed or deployed. If the assembly is not a JAR
archive, we also need to declare its type.
Configuring the project assembly in top-level POM.
<project>
...
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-2</version>
<executions>
<execution>
<id>create-project-bundle</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>project</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
...
</project>
Each project POM references the managed plugin configuration from Configuring the project assembly in top-level POM using a minimal plugin declaration in its build section shown in Activating the Assembly Plugin Configuration in Child Projects.
Activating the Assembly Plugin Configuration in Child Projects.
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
</plugins>
</build>
To produce the set of project assemblies, run mvn install from the
top-level directory. You should see Maven installing artifacts with
classifiers in your local repository.
$ mvn install ... Installing ~/mvn-examples-1.0/assemblies/as-dependencies/project-parent/\ second-project/target/second-project-1.0-SNAPSHOT-project.tar.gz to ~/.m2/repository/org/sonatype/mavenbook/assemblies/second-project/1.0-SNAPSHOT/\ second-project-1.0-SNAPSHOT-project.tar.gz ... Installing ~/mvn-examples-1.0/assemblies/as-dependencies/project-parent/\ second-project/target/second-project-1.0-SNAPSHOT-project.tar.bz2 to ~/.m2/repository/org/sonatype/mavenbook/assemblies/second-project/1.0-SNAPSHOT/\ second-project-1.0-SNAPSHOT-project.tar.bz2 ... Installing ~/mvn-examples-1.0/assemblies/as-dependencies/project-parent/\ second-project/target/second-project-1.0-SNAPSHOT-project.zip to ~/.m2/repository/org/sonatype/mavenbook/assemblies/second-project/1.0-SNAPSHOT/\\ second-project-1.0-SNAPSHOT-project.zip ...
When you run install, Maven will copy each project’s main artifact and each assembly to your local Maven repository. All of these artifacts are now available for reference as dependencies in other projects locally. If your ultimate goal is to create a bundle which includes assemblies from multiple projects, you can do so by creating another project which will include other project’s assemblies as dependencies. This bundling project (aptly named project-bundle) is responsible for creating the bundled assembly. The POM for the bundling project would resemble the XML document listed in POM for the Assembly Bundling Project.
POM for the Assembly Bundling Project.
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook.assemblies</groupId>
<artifactId>project-bundle</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Assemblies-as-Dependencies Example Project Bundle</name>
<url>http://sonatype.com/book</url>
<dependencies>
<dependency>
<groupId>org.sonatype.mavenbook.assemblies</groupId>
<artifactId>first-project</artifactId>
<version>1.0-SNAPSHOT</version>
<classifier>project</classifier>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.sonatype.mavenbook.assemblies</groupId>
<artifactId>second-project</artifactId>
<version>1.0-SNAPSHOT</version>
<classifier>project</classifier>
<type>zip</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-2</version>
<executions>
<execution>
<id>bundle-project-sources</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>
jar-with-dependencies
</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
This bundling project’s POM references the two assemblies from
first-project and second-project. Instead of referencing the main
artifact of each project, the bundling project’s POM specifies a
classifier of project and a type of zip. This tells Maven to
resolve the ZIP archive which was created by the project
assembly. Note that the bundling project generates a
jar-with-dependencies assembly. jar-with-dependencies does not
create a particularly elegant bundle, it simply creates a JAR file
with the unpacked contents of all of the
dependencies. jar-with-dependencies is really just telling Maven to
take all of the dependencies, unpack them, and then create a single
archive which includes the output of the current project. In this
project, it has the effect of creating a single JAR file that puts the
two project assemblies from first-project and second-project
side-by-side.
This example illustrates how the basic capabilities of the Maven
Assembly plugin can be combined without the need for a custom assembly
descriptor. It achieves the purpose of creating a single archive that
contains the project directories for multiple projects
side-by-side. This time, the jar-with-dependencies is just a storage
format, so we don’t need to specify a Main-Class manifest
attribute. To build the bundle, we just build the project-bundle
project normally:
$ mvn package
...
[INFO] [assembly:single {execution: bundle-project-sources}]
[INFO] Processing DependencySet (output=)
[INFO] Building jar: ~/downloads/mvn-examples-1.0/assemblies/as-dependencies/\
project-bundle/target/project-bundle-1.0-SNAPSHOT-jar-with-dependencies.jar
To verify that the project-bundle assembly contains the unpacked
contents of the assembly dependencies, run jar tf:
$ jar tf \ target/project-bundle-1.0-SNAPSHOT-jar-with-dependencies.jar ... first-project-1.0-SNAPSHOT/pom.xml first-project-1.0-SNAPSHOT/src/main/java/org/sonatype/mavenbook/App.java first-project-1.0-SNAPSHOT/src/test/java/org/sonatype/mavenbook/AppTest.java ... second-project-1.0-SNAPSHOT/pom.xml second-project-1.0-SNAPSHOT/src/main/java/org/sonatype/mavenbook/App.java second-project-1.0-SNAPSHOT/src/test/java/org/sonatype/mavenbook/AppTest.java
After reading this section, the title should make more sense. You’ve assembled assemblies from two projects into an assembly using a bundling project which has a dependency on each of the assemblies.
