Maven Multi module tutorial

In this tutorial we will show how to create and use a Maven multi-module project. As an example, we will use an EJB application which includes a client and server module.

Maven Reactor in a nutshell

The mechanism in Maven that handles multi-module projects is the Reactor. This part of the Maven core does the following:

  •  Collects all the available modules to build
  •  Sorts the projects into the correct build order
  •  Builds the selected projects in order

A multi-module project starts from a parent pom.xml file whch references one or more submodules. In the root directory, you will find the parent POM (aka the top-level POM). In the subfolder you will find all the submodules used by the project.

maven multimodule maven tutorial jboss

Here’s a simple top-level Project pom.xml from https://github.com/wildfly/quickstart/tree/main/ejb-remote

<?xml version="1.0" encoding="UTF-8"?>

<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>
    <parent>
        <groupId>org.wildfly.quickstarts</groupId>
        <artifactId>wildfly-quickstart-parent</artifactId>
        <!--
        Maintain separation between the artifact id and the version to help prevent
        merge conflicts between commits changing the GA and those changing the V.
        -->
        <version>2</version>
        <relativePath/>
    </parent>
    <artifactId>ejb-remote</artifactId>
    <version>27.0.0.Beta1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Quickstart: ejb-remote</name>
    <description>This project demonstrates how to access an EJB from a remote client</description>

    <dependencyManagement>
        <dependencies>
            <!-- importing the jakartaee8-with-tools BOM adds specs and other useful artifacts as managed dependencies -->
            <dependency>
                <groupId>org.wildfly.bom</groupId>
                <artifactId>wildfly-jakartaee8-with-tools</artifactId>
                <version>${version.server.bom}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <modules>
        <module>server-side</module>
        <module>client</module>
    </modules>

    <build>
        <plugins>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Notice that the pom.xml in turn references a parent project org.wildfly.quickstarts:wildfly-quickstart-parent:2  . The parent project doesn’t create a JAR or a WAR; instead, it is simply a POM that refers to other Maven projects. The appropriate packaging for a project like simple-parent that simply provides a Project Object Model is pom.

The next section in the pom.xml lists the project’s sub-modules:

<modules>
        <module>server-side</module>
        <module>client</module>
</modules>

There, each module element corresponds to a subdirectory beneath your top level pom.xml. Maven knows to look in these directories for pom.xml files, and it will add sub modules to the list of Maven projects included in a build.

Lastly, we define some settings which will be inherited by all sub modules. The setting we want to export is the org.wildfly.plugins which will be used both by the server and the client application. You can add here as well common dependencies and POM inheritance will export these dependencies to all submodules.

Next, let’s have a look at the sub modules:

  • The server sub module: this module builds and installs the EJB on the server
  • The client sub module: this module builds and runs the EJB Client

The server-side submodule:

The first sub-module is the server-side module which builds with the following pom.xml:

<?xml version="1.0" encoding="UTF-8"?>

<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>
    <parent>
        <groupId>org.wildfly.quickstarts</groupId>
        <artifactId>ejb-remote</artifactId>
        <version>27.0.0.Beta1-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>ejb-remote-server-side</artifactId>
    <packaging>ejb</packaging>
    <name>Quickstart: ejb-remote - server-side</name>
    <description>This project demonstrates how to access an EJB from a remote client; this is the server side POM file</description>

    <dependencies>

        <!-- Import the Common Annotations API (JSR-250), we use provided scope
            as the API is included in JBoss EAP -->
        <dependency>
            <groupId>org.jboss.spec.javax.annotation</groupId>
            <artifactId>jboss-annotations-api_1.3_spec</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- Import the EJB API, we use provided scope as the API is included in JBoss EAP -->
        <dependency>
            <groupId>org.jboss.spec.javax.ejb</groupId>
            <artifactId>jboss-ejb-api_3.2_spec</artifactId>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- WildFly plug-in to deploy the application -->
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <configuration>
                    <skip>false</skip>
                    <filename>${project.build.finalName}.jar</filename>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ejb-plugin</artifactId>
                <configuration>
                    <ejbVersion>3.2</ejbVersion>
                    <!-- this is false by default -->
                    <generateClient>true</generateClient>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>

Two things to notice:

Firstly, notice how you can refer to the parent pom.xml from the sub module:

<parent>
        <groupId>org.wildfly.quickstarts</groupId>
        <artifactId>ejb-remote</artifactId>
        <version>27.0.0.Beta1-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
</parent>

Next, notice this project uses as packaging “ejb”:

<packaging>ejb</packaging>

This, in turn, needs the maven-ejb-plugin. This plugin does not do any specific EJB processing however you can use it to generate EJB client classes if you set the generateClient element to true.

The client submodule:

The client submodule will let you build and run the remote client application. Here is the pom.xml:

<?xml version="1.0" encoding="UTF-8"?>

<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>

    <parent>
        <groupId>org.wildfly.quickstarts</groupId>
        <artifactId>ejb-remote</artifactId>
        <version>27.0.0.Beta1-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>ejb-remote-client</artifactId>
    <packaging>jar</packaging>
    <name>Quickstart: ejb-remote - client</name>
    <description>This project demonstrates how to access an EJB from a remote client; this is the client POM file</description>

    <properties>
        <http>false</http>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.wildfly</groupId>
            <artifactId>wildfly-ejb-client-bom</artifactId>
            <type>pom</type>
            <scope>compile</scope>
        </dependency>

        <!-- We depend on the EJB remote business interfaces of this application -->
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>ejb-remote-server-side</artifactId>
            <version>${project.version}</version>
            <type>ejb-client</type>
        </dependency>

    </dependencies>

    <build>
        <plugins>

            <!-- Add the maven exec plug-in to allow us to run a java program
                via maven -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <configuration>
                    <executable>java</executable>
                    <workingDirectory>${project.build.directory}/exec-working-directory</workingDirectory>
                    <arguments>
                        <argument>-Dhttp=${http}</argument>
                        <argument>-classpath</argument>
                        <classpath></classpath>
                        <argument>org.jboss.as.quickstarts.ejb.remote.client.RemoteEJBClient</argument>
                    </arguments>
                    <!--<detail>true</detail>-->
                </configuration>
            </plugin>
            <!-- build standalone exe jar -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>org.jboss.as.quickstarts.ejb.remote.client.RemoteEJBClient</mainClass>
                        </manifest>
                        <manifestEntries>
                            <Multi-Release>true</Multi-Release>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>

    </build>

</project>

Please notice the above POM uses as packaging:

<packaging>jar</packaging>

This is because you need a JAR file at the end of the build phase. Additionally it contains the maven exec plugin to allow us to run a java program via maven.

Running the example application:

Compiling and installing both sub-modules (in the correct order in case sub-modules are interdependent) is essential. You can do that by navigating to the parent POM and running the following command:

$ mvn clean install

Thus, executing the build phase on the parent project automatically gets executed for all its child projects in the correct order:

[INFO] Reactor Summary for Quickstart: ejb-remote 26.0.1.Final:
[INFO] 
[INFO] Quickstart: ejb-remote ............................. SUCCESS [  1.017 s]
[INFO] Quickstart: ejb-remote - server-side ............... SUCCESS [  5.016 s]
[INFO] Quickstart: ejb-remote - client .................... SUCCESS [ 45.466 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  51.650 s
[INFO] Finished at: 2022-05-04T17:19:11+02:00
[INFO] ------------------------------------------------------------------------

Next, in order to test the EJB application, head to the server-side folder and deploy the EJB as follows:

mvn install wildfly:deploy

Then, move to the client folder and run the Client EJB as follows:

mvn install exec:java

That’s all. In this tutorial we have learnt how to create a multi module Maven project to build and test a client/server application.