Creating Production Artifacts in A Multi-Module Maven Project

If you are working with Java projects, you will most likely have to deal with Maven. Maven is a project management tool, used as an industry standard for medium to large scale Java applications. In this post, I will teach you to create production artifacts of multi-module Maven projects using the assembly plugin.

Initial project structure

For the purposes of this article, I will assume a very bare-bones project in Maven. We will have 4 modules: root, which is the main (container) module, common, which is used by other modules, app, the app itself, and tools. We are not writing any actual code here, so do not worry about the logic. Here is the dependency tree for these modules:

POM files

Now, we need to create POM files for these modules. Firstly, the root module:

This is the umbrella module that holds the common, app, and tools together. You can see them enumerated on lines 11-15. Another important aspect is the pom packaging mode on line 9. It is used for modules that contain of other modules. Now, the POM for the common module:

Notice right away the jar packaging mode on line 10. Since this is a standalone module, it does not need the pom mode, we will be building a JAR file. On lines 5-9 we define the module as a child of root, and add some dependencies on lines 15-26.

Lastly, here are POM files for the app and tools:

These 2 look almost the same. They both compile as JAR files and depend on common (to show you inter-module dependency resolution).

At this moment, you can run mvn package in root and get a JAR file for each of your projects (in <module name>/target). This is very good, but most large project need more fine-tuning in regards of production artifacts. This is where the assembly plugin is going to help out.

maven-assembly-plugin

maven-assembly-plugin is used to assemble all modules into 1 entity, while providing overrides for all aspects of the build (as always, convention over configuration). To make it work, we need to add another module to our project, we will call it packaging:

This is, again, a standard POM file, with the pom packaging mode (since packaging module does not need a JAR file). Now, to connect the maven-assembly-plugin, add the following code on line 13:

This is a lot, so let’s go step-by-step. Firstly, we define the desired plugin coordinates on lines 4-6. Then, we set it up to run during the package build phase on lines 10-13 (it happens when you run mvn package). Lastly, we specify the assembly.xml (coming in the next step) file as the assembly descriptor. This is effectively a configuration file for the plugin which describes how do we want our artifact to be built.

Lastly, let’s add the app and tools modules as dependencies for packaging, to make sure Maven builds them before the packaging module:

Assembly descriptor

Assembly descriptor is an XML file that specifies how the production artifact should be structured. Create a file called assembly.xml in the packaging module folder:

This is quite a big file, but I assure you there is nothing confusing going on there. Firstly, one line 3, we set the name for this particular assembly (production-artifact, in our case). Then, on line 5 we set the format to dir. This means the resulting artifact will be a folder with JAR files in it. As an alternative, you can change that to jar and get one BIG jar file with all resources and dependencies. On lines 7-17 we define the dependencies. We say that (line 9) do not use the artifact from the packaging module itself, (line 10) put the dependencies in the shared folder, (11) keep them as JAR files instead of unpacking, and (lines 12-15) exclude the app and tools from the dependency list.

On lines 18-30 we define the modules included in this artifact. On line 20 we let the plugin know to discover all modules managed by maven and (line 22-23) include only the app and tools ones. We do not specify common here, because it will be included as a dependency under 7-17. I did this to show you how you can use Maven modules as both means to an end (executable) or a library (dependency). Lastly, on lines 26-27 we say to keep the artifacts as JAR and do not include the dependencies in the JAR files directly. They are stored in a separate folder for a number of reasons: saving space, cutting the startup time, dynamic updates, and so on.

Finally, you can run mvn package in project root and gaze upon your magnificent production build (under packaging/target):

Closing notes

Thank you for reading, this is my first time writing about Java, I hope you liked it.

Resources

Get new content delivered to your mailbox:

leave a comment