Maven Multi module tutorial

In this tutorial we will show how to use Maven multi-module projects using as sample a remote EJB client application.

The mechanism in Maven that handles multi-module projects is referred to as 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 is defined by a parent POM referencing one or more submodules. In root directory, you will find the parent POM (also called 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

<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>com.sample</groupId>
    <artifactId>ejb-parent</artifactId>
    <version>7.1.2-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>JBoss AS Quickstarts: Parent for remote EJB and Java client</name>

    <licenses>
        <license>
            <name>Apache License, Version 2.0</name>
            <distribution>repo</distribution>
            <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
        </license>
    </licenses>
    
    <properties>

        <version.org.jboss.as.plugins.maven.plugin>7.3.Final</version.org.jboss.as.plugins.maven.plugin>
    </properties>


    <modules>
        <module>server</module>
        <module>client</module>
    </modules>

   <build>
      <plugins>

            <plugin>
               <groupId>org.jboss.as.plugins</groupId>
               <artifactId>jboss-as-maven-plugin</artifactId>
               <version>${version.org.jboss.as.plugins.maven.plugin}</version>
               <inherited>true</inherited>
               <configuration>
                  <skip>true</skip>
               </configuration>
            </plugin>
         </plugins>
   </build>

</project>

Notice that the parent defines a set of Maven coordinates: the groupId is com.sample, the artifactId is simple-parent . 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 submodules:


<modules>
        <module>server</module>
        <module>client</module>
</modules>

These modules are defined in the modules element, and 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 submodules to the list of Maven projects included in a build.

Lastly, we define some settings which will be inherited by all submodules. The setting we want to export is the org.jboss.as.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.


Now let’s have a look at the submodules contained in this project which are two:

 

  • The server submodule: this module is used to compile package and deploy a simple stateless EJB
  • The client submodule: this module is used to compile and run test your remote EJB

The server submodule:

 

The first submodule is the server module which will be used to pack your EJB into a ${project.build.finalName}.jar as defined by the <filename> entry.

<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>com.sample</groupId>
   <artifactId>ejb-remote</artifactId>
   <version>7.1.2-SNAPSHOT</version>
   <packaging>ejb</packaging>
   <name>JBoss AS Quickstarts: Server side of remote EJB</name>

   <url>http://jboss.org/jbossas</url>
   <licenses>
      <license>
         <name>Apache License, Version 2.0</name>
         <distribution>repo</distribution>
         <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
      </license>
   </licenses>

   <properties>

      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

       <!-- JBoss dependency versions -->
       <version.org.jboss.as.plugins.maven.plugin>7.3.Final</version.org.jboss.as.plugins.maven.plugin>
       <version.org.jboss.spec.jboss.javaee.6.0>3.0.0.Final</version.org.jboss.spec.jboss.javaee.6.0>

       <!-- other plugin versions -->
       <version.compiler.plugin>2.3.1</version.compiler.plugin>
       <version.ejb.plugin>2.3</version.ejb.plugin>

       <!-- maven-compiler-plugin -->
       <maven.compiler.target>1.6</maven.compiler.target>
       <maven.compiler.source>1.6</maven.compiler.source>
   </properties>

   <dependencyManagement>
      <dependencies>

         <dependency>
            <groupId>org.jboss.spec</groupId>
            <artifactId>jboss-javaee-6.0</artifactId>
            <version>${version.org.jboss.spec.jboss.javaee.6.0}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <dependencies>

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

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

   </dependencies>

   <build>
      <!-- Set the name of the deployment -->
      <finalName>jboss-as-ejb-remote-app</finalName>
      <plugins>
         <!-- JBoss AS plugin to deploy the application -->
         <plugin>
            <groupId>org.jboss.as.plugins</groupId>
            <artifactId>jboss-as-maven-plugin</artifactId>
            <version>${version.org.jboss.as.plugins.maven.plugin}</version>
            <configuration>
                <filename>${project.build.finalName}.jar</filename>
            </configuration>
         </plugin>

         <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
             <version>${version.compiler.plugin}</version>
             <configuration>
                 <source>${maven.compiler.source}</source>
                 <target>${maven.compiler.target}</target>
            </configuration>
         </plugin>
      <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-ejb-plugin</artifactId>
            <version>${version.ejb.plugin}</version>
            <configuration>
               <ejbVersion>3.1</ejbVersion>
               <!-- this is false by default -->
               <generateClient>true</generateClient>
            </configuration>
         </plugin>

      </plugins>
   </build>

</project>

Notice this project uses as packaging
  <packaging>ejb</packaging>
which needs the maven-ejb-plugin. This plugin does not do any specific EJB processing however it ca be used to generate EJB client classes if you set the generateClient element to true.

The client submodule:

The client submodule will be used to run the remote client application. It is packaged in a JAR file as specified by:
   <packaging>jar</packaging>
Additionally it contains the maven exec plugin to allow us to run a java program via maven.

<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>com.sample</groupId>
   <artifactId>ejb-client</artifactId>
   <version>7.1.2-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>JBoss AS Quickstarts: EJB Remote Client</name>
   <description>JBoss AS Quickstarts: Java client for remote EJB</description>

   <url>http://jboss.org/jbossas</url>
   <licenses>
      <license>
         <name>Apache License, Version 2.0</name>
         <distribution>repo</distribution>
         <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
      </license>
   </licenses>

   <properties>

      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

       <!-- JBoss dependency versions -->
       <version.org.jboss.as>7.1.1.Final</version.org.jboss.as>
       <version.org.jboss.as.plugins.maven.plugin>7.3.Final</version.org.jboss.as.plugins.maven.plugin>
       <version.org.jboss.spec.jboss.javaee.6.0>3.0.0.Final</version.org.jboss.spec.jboss.javaee.6.0>

       <!-- other plugin versions -->
       <version.compiler.plugin>2.3.1</version.compiler.plugin>
       <version.exec.plugin>1.2.1</version.exec.plugin>
       <version.war.plugin>2.1.1</version.war.plugin>

       <!-- maven-compiler-plugin -->
       <maven.compiler.target>1.6</maven.compiler.target>
       <maven.compiler.source>1.6</maven.compiler.source>
   </properties>

   <dependencyManagement>
      <dependencies>

         <dependency>
            <groupId>org.jboss.spec</groupId>
            <artifactId>jboss-javaee-6.0</artifactId>
            <version>${version.org.jboss.spec.jboss.javaee.6.0}</version>
            <type>pom</type>
            <scope>import</scope>
         </dependency>

         <dependency>
             <groupId>org.jboss.as</groupId>
             <artifactId>jboss-as-ejb-client-bom</artifactId>
             <version>${version.org.jboss.as}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
      </dependencies>
   </dependencyManagement>

   <dependencies>

       <!-- Import the transaction spec API, we use runtime scope because we aren't using any direct
        reference to the spec API in our client code -->
      <dependency>
         <groupId>org.jboss.spec.javax.transaction</groupId>
         <artifactId>jboss-transaction-api_1.1_spec</artifactId>
         <scope>runtime</scope>
      </dependency>

      <!-- Import the EJB 3.1 API, we use runtime scope because we aren't using any direct
       reference to EJB spec API in our client code -->
      <dependency>
         <groupId>org.jboss.spec.javax.ejb</groupId>
         <artifactId>jboss-ejb-api_3.1_spec</artifactId>
         <scope>runtime</scope>
      </dependency>

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

       <!-- JBoss EJB client API jar. We use runtime scope because the EJB client API
        isn't directly used in this example. We just need it in our runtime classpath -->
       <dependency>
           <groupId>org.jboss</groupId>
           <artifactId>jboss-ejb-client</artifactId>
           <scope>runtime</scope>
       </dependency>

       <!-- client communications with the server use XNIO -->
       <dependency>
           <groupId>org.jboss.xnio</groupId>
           <artifactId>xnio-api</artifactId>
           <scope>runtime</scope>
       </dependency>

       <dependency>
           <groupId>org.jboss.xnio</groupId>
           <artifactId>xnio-nio</artifactId>
           <scope>runtime</scope>
       </dependency>

      <!-- The client needs JBoss remoting to access the server -->
       <dependency>
            <groupId>org.jboss.remoting3</groupId>
            <artifactId>jboss-remoting</artifactId>
            <scope>runtime</scope>
        </dependency>

      <!-- Remote EJB accesses can be secured -->
       <dependency>
           <groupId>org.jboss.sasl</groupId>
           <artifactId>jboss-sasl</artifactId>
           <scope>runtime</scope>
       </dependency>

       <!-- data serialization for invoking remote EJBs -->
       <dependency>
           <groupId>org.jboss.marshalling</groupId>
           <artifactId>jboss-marshalling-river</artifactId>
           <scope>runtime</scope>
       </dependency>

   </dependencies>

   <build>
      <plugins>
         <!-- Enforce Java 1.6  -->
         <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
             <version>${version.compiler.plugin}</version>
             <configuration>
                 <source>${maven.compiler.source}</source>
                 <target>${maven.compiler.target}</target>
            </configuration>
         </plugin>

         <!-- Add the maven exec plugin to allow us to run a java program via maven -->
         <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>${version.exec.plugin}</version>
            <executions>
              <execution>
                  <goals>
                     <goal>exec</goal>
                  </goals>
              </execution>
            </executions>
            <configuration>
              <executable>java</executable>
              <workingDirectory>${project.build.directory}/exec-working-directory</workingDirectory>
              <arguments>
                <!-- automatically creates the classpath using all project dependencies,
                     also adding the project build directory -->
                <argument>-classpath</argument>
                <classpath>
                </classpath>
                <argument>com.sample.client.RemoteEJBClient</argument>
              </arguments>
            </configuration>
        </plugin>
            <!-- build standalone exe jar -->
         <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
               <descriptorRefs>
                  <descriptorRef>jar-with-dependencies</descriptorRef>
               </descriptorRefs>
               <archive>
                  <manifest>
                     <mainClass>com.sample.client.RemoteEJBClient</mainClass>
                  </manifest>
               </archive>
            </configuration>
         </plugin>

            <plugin>
               <groupId>org.jboss.as.plugins</groupId>
               <artifactId>jboss-as-maven-plugin</artifactId>
               <version>${version.org.jboss.as.plugins.maven.plugin}</version>
               <inherited>true</inherited>
               <configuration>
                  <skip>true</skip>
               </configuration>
            </plugin>
      </plugins>

   </build>

</project>

Running the example application:

Compiling and installing both sub-modules (in the correct order in case sub-modules are interdependent) is essential. It can be done in the command line by navigating to the parent POM folder and running the following command:

$ mvn clean install

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

[INFO] Reactor Summary:
[INFO]
[INFO] JBoss AS Quickstarts: Server side of remote EJB ... SUCCESS [7.393s]
[INFO] JBoss AS Quickstarts: EJB Remote Client ........... SUCCESS [24.523s]
[INFO] JBoss AS Quickstarts: Parent for remote EJB and Java client  SUCCESS [0.0
25s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 32.515s
[INFO] Finished at: Fri Dec 28 16:31:49 CET 2012
[INFO] Final Memory: 7M/19M
[INFO] ------------------------------------------------------------------------

In order to run your client application you need to issue:

$ mvn clean compile

$ mvn exec:exec
 

Now navigate to the server module and execute the following Maven task:
Next, provided that you have a running JBoss AS 7 instance

mvn jboss-as:deploy

This will deploy the EJB on a running JBoss AS 7 instance. Finally, navigate to the client subdirectory and issue:

mvn exec:exec

This will recall the remote EJB client application.
 You can download the full EJB 3 remote client for JBoss AS 7 at: https://github.com/fmarchioni/jboss-as-quickstart/tree/master/ejb-remote-client