Generic Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

Apache Maven is a powerful tool that uses plugins to automate and perform all the build and reporting tasks in a Java project.

However, there are likely to be several of these plugins used in the build along with different versions and configurations, especially in a multi-module project. This can lead to problems of complex POM files with redundant or duplicate plugin artifacts as well as configurations scattered across various child projects.

In this article, we'll see how to use Maven's dependency management mechanism to handle such issues and effectively maintain plugins across the whole project.

2. Plugin Configuration

Maven has two types of plugin:

  • Build – executed during the build process. Examples include Clean, Install, and Surefire plugins. These should be configured in the build section of the POM.
  • Reporting – executed during site generation to produce various project reports. Examples include Javadoc and Checkstyle plugins. These are configured in the reporting section of the project POM.

Maven plugins provide all the useful functionalities required to execute and manage the project build.

For example, we can declare the Jar plugin in the POM:

<build>
    ....
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            ....
        </plugin>
    ....
    </plugins>
</build>

Here, we've included the plugin in the build section to add the capability to compile our project into a jar.

3. Plugin Management

In addition to the plugins, we can also declare plugins in the pluginManagement section of the POM. This contains the plugin elements in much the same way as we saw previously. However, by adding the plugin in the pluginManagement section, it becomes available to this POM, and all inheriting child POMs.

This means that any child POMs will inherit the plugin executions simply by referencing the plugin in their plugin section. All we need to do is add the relevant groupId and artifactId, without having to duplicate the configuration or manage the version.

Similar to the dependency management mechanism, this is particularly useful in multi-module projects as it provides a central location to manage plugin versions and any related configuration.

4. Example

Let's start by creating a simple multi-module project with two submodules. We'll include the Build Helper Plugin in the parent POM which contains several small goals to assist with the build lifecycle. In our example, we'll use it to copy some additional resources to the project output for a child project.

4.1. Parent POM Configuration

First, we'll add the plugin to the pluginManagement section of the parent POM:

<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.2.0</version>
            <executions>
                <execution>
                    <id>add-resource</id>
                    <phase>generate-resources</phase>
                    <goals>
                        <goal>add-resource</goal>
                    </goals>
                    <configuration>
                        <resources>
                            <resource>
                                <directory>src/resources</directory>
                                <targetPath>json</targetPath>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
   </plugins>
</pluginManagement>

This binds the plugin's add-resource goal to the generate-resources phase in the default POM lifecycle. We've also specified the src/resources directory containing the additional resources. The plugin execution will copy these resources to the target location in the project output, as required.

Next, let's run the maven command to ensure that the configuration is valid and the build is successful:

$ mvn clean test

After running this, the target location does not contain the expected resources yet.

4.2. Child POM Configuration

Now, let's reference this plugin from the child POM:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Similar to dependency management, we don't declare the version or any plugin configuration. Instead, child projects inherit these values from the declaration in the parent POM.

Finally, let's run the build again and see the output:

....
[INFO] --- build-helper-maven-plugin:3.2.0:add-resource (add-resource) @ submodule-1 ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ submodule-1 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.

[INFO] Copying 1 resource to json
....

Here, the plugin executes during the build but only in the child project with the corresponding declaration. As a result, the project output now contains the additional resources from the specified project location, as expected.

We should note that only the parent POM contains the plugin declaration and configuration whilst the child projects just reference this, as needed.

The child projects are free to modify the inherited configuration, if required.

5. Core Plugins

There are some Maven core plugins that are used as part of the build lifecycle, by default. For example, the clean and compiler plugins don't need to be declared explicitly.

We can, however, explicitly declare and configure these in the pluginManagement element in the POM. The main difference is that the core plugin configuration takes effect automatically without any reference in the child projects.

Let's try this out by adding the compiler plugin to the familiar pluginManagement section:

<pluginManagement>
    ....
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
    ....
</pluginManagement>

Here, we've locked down the plugin version and configured it to use Java 8 to build the project. However, there is no additional plugin declaration required in any child projects. The build framework activates this configuration by default. Therefore, this configuration means that the build must use Java 8 to compile this project across all modules.

Overall, it can be good practice to explicitly declare the configuration and lockdown the versions for any plugins required in a multi-module project. Consequently, different child projects can inherit only the required plugin configurations from the parent POM and apply them as needed.

This eliminates duplicate declarations, simplifies the POM files, and improves build reproducibility.

6. Conclusion

In this article, we've seen how to centralize and manage the Maven plugins required for building a project.

First, we looked at plugins and their usage in the project POM. Then we took a more detailed look at the Maven plugin management mechanism and how this helps to reduce duplication and improve the maintainability of the build configuration.

As always, the example code is available over on GitHub.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
Comments are closed on this article!