Creating a multi-module application

duration 30 minutes

Prerequisites:

You will learn how to build an application with multiple modules with Maven and Open Liberty.

What you’ll learn

A Jakarta Platform, Enterprise Edition (Jakarta EE) application consists of modules that work together as one entity. An enterprise archive (EAR) is a wrapper for a Jakarta EE application, which consists of web archive (WAR) and Java archive (JAR) files. To deploy or distribute the Jakarta EE application into new environments, all the modules and resources must first be packaged into an EAR file.

In this guide, you will learn how to:

  • establish a dependency between a web module and a Java library module,

  • use Maven to package the WAR file and the JAR file into an EAR file so that you can run and test the application on Open Liberty, and

  • use Liberty Maven plug-in to develop a multi-module application in development mode without having to prebuild the JAR and WAR files. In development mode, your changes are automatically picked up by the running server.

You will build a unit converter application that converts heights from centimeters into feet and inches. The application will request the user to enter a height value in centimeters. Then, the application processes the input by using functions that are found in the JAR file to return the height value in imperial units.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-maven-multimodules.git
cd guide-maven-multimodules

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Access partial implementation of the application from the start folder. This folder includes a web module in the war folder, a Java library in the jar folder, and template files in the ear folder. However, the Java library and the web module are independent projects, and you will need to complete the following steps to implement the application:

  1. Add a dependency relationship between the two modules.

  2. Assemble the entire application into an EAR file.

  3. Aggregate the entire build.

  4. Test the multi-module application.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build the application:

cd finish
mvn install

To deploy your EAR application on an Open Liberty server, run the Maven liberty:run goal from the finish directory using the -pl flag to specify the ear project. The -pl flag specifies the project where the Maven goal runs.

mvn -pl ear liberty:run

Once the server is running, you can find the application at the following URL: http://localhost:9080/converter/

After you are finished checking out the application, stop the Open Liberty server by pressing CTRL+C in the command-line session where you ran the server. Alternatively, you can run the liberty:stop goal using the -pl ear flag from the finish directory in another command-line session:

mvn -pl ear liberty:stop

Adding dependencies between WAR and JAR modules

To use a Java library in your web module, you must add a dependency relationship between the two modules.

As you might have noticed, each module has its own pom.xml file. Each module has its own pom.xml file because each module is treated as an independent project. You can rebuild, reuse, and reassemble every module on its own.

Navigate to the start directory to begin.

Replace the war/POM file.
war/pom.xml

war/pom.xml

 1<?xml version='1.0' encoding='utf-8'?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0"
 3    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
 5    http://maven.apache.org/xsd/maven-4.0.0.xsd">
 6
 7    <!-- tag::parent[] -->
 8    <parent>
 9        <groupId>io.openliberty.guides</groupId>
10        <artifactId>guide-maven-multimodules</artifactId>
11        <version>1.0-SNAPSHOT</version>
12    </parent>
13    <!-- end::parent[] -->
14
15    <modelVersion>4.0.0</modelVersion>
16
17    <groupId>io.openliberty.guides</groupId>
18    <artifactId>guide-maven-multimodules-war</artifactId>
19    <packaging>war</packaging>
20    <version>1.0-SNAPSHOT</version>
21    <name>guide-maven-multimodules-war</name>
22    <url>http://maven.apache.org</url>
23
24    <properties>
25        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
26        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
27        <maven.compiler.source>1.8</maven.compiler.source>
28        <maven.compiler.target>1.8</maven.compiler.target>
29    </properties>
30
31    <dependencies>
32        <!-- Provided dependencies -->
33        <dependency>
34            <groupId>javax.servlet</groupId>
35            <artifactId>javax.servlet-api</artifactId>
36            <version>4.0.1</version>
37            <scope>provided</scope>
38        </dependency>
39        <dependency>
40            <groupId>jakarta.platform</groupId>
41            <artifactId>jakarta.jakartaee-api</artifactId>
42            <version>9.1.0</version>
43            <scope>provided</scope>
44        </dependency>
45        <dependency>
46            <groupId>org.eclipse.microprofile</groupId>
47            <artifactId>microprofile</artifactId>
48            <version>5.0</version>
49            <type>pom</type>
50            <scope>provided</scope>
51        </dependency>
52
53        <!-- tag::dependency[] -->
54        <dependency>
55            <groupId>io.openliberty.guides</groupId>
56            <artifactId>guide-maven-multimodules-jar</artifactId>
57            <version>1.0-SNAPSHOT</version>
58        </dependency>
59        <!-- end::dependency[] -->
60
61    </dependencies>
62
63</project>

The added dependency element is the Java library module that implements the functions that you need for the unit converter.

Although the parent/child structure is not normally needed for multi-module applications, adding it helps us to better organize all of the projects. This structure allows all of the child projects to make use of the plugins that are defined in the parent pom.xml file, without having to define them again in the child pom.xml files.

Assembling multiple modules into an EAR file

To deploy the entire application on the Open Liberty server, first package the application. Use the EAR project to assemble multiple modules into an EAR file.

Navigate to the ear folder and find a template pom.xml file.

Replace the ear/POM file.
ear/pom.xml

ear/pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<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
  3    http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4
  5    <parent>
  6        <groupId>io.openliberty.guides</groupId>
  7        <artifactId>guide-maven-multimodules</artifactId>
  8        <version>1.0-SNAPSHOT</version>
  9    </parent>
 10
 11    <modelVersion>4.0.0</modelVersion>
 12
 13    <!-- tag::packaging[] -->
 14    <groupId>io.openliberty.guides</groupId>
 15    <artifactId>guide-maven-multimodules-ear</artifactId>
 16    <version>1.0-SNAPSHOT</version>
 17    <!-- tag::packagingType[] -->
 18    <packaging>ear</packaging>
 19    <!-- end::packagingType[] -->
 20    <!-- end::packaging[] -->
 21
 22    <properties>
 23        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 24        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 25        <maven.compiler.source>1.8</maven.compiler.source>
 26        <maven.compiler.target>1.8</maven.compiler.target>
 27        <!-- Liberty configuration -->
 28        <liberty.var.default.http.port>9080</liberty.var.default.http.port>
 29        <liberty.var.default.https.port>9443</liberty.var.default.https.port>
 30    </properties>
 31
 32    <dependencies>
 33        <!-- web and jar modules as dependencies -->
 34        <!-- tag::dependencies[] -->
 35        <!-- tag::dependency-jar[] -->
 36        <dependency>
 37            <groupId>io.openliberty.guides</groupId>
 38            <artifactId>guide-maven-multimodules-jar</artifactId>
 39            <version>1.0-SNAPSHOT</version>
 40            <type>jar</type>
 41        </dependency>
 42        <!-- end::dependency-jar[] -->
 43        <!-- tag::dependency-war[] -->
 44        <dependency>
 45            <groupId>io.openliberty.guides</groupId>
 46            <artifactId>guide-maven-multimodules-war</artifactId>
 47            <version>1.0-SNAPSHOT</version>
 48            <!-- tag::warType[] -->
 49            <type>war</type>
 50            <!-- end::warType[] -->
 51        </dependency>
 52        <!-- end::dependency-war[] -->
 53        <!-- end::dependencies[] -->
 54
 55        <!-- For tests -->
 56        <dependency>
 57            <groupId>org.junit.jupiter</groupId>
 58            <artifactId>junit-jupiter</artifactId>
 59            <version>5.8.2</version>
 60            <scope>test</scope>
 61        </dependency>
 62    </dependencies>
 63
 64    <build>
 65        <finalName>${project.artifactId}</finalName>
 66        <plugins>
 67            <!-- tag::maven-ear-plugin[] -->
 68            <plugin>
 69                <groupId>org.apache.maven.plugins</groupId>
 70                <artifactId>maven-ear-plugin</artifactId>
 71                <version>3.2.0</version>
 72                <configuration>
 73                    <modules>
 74                        <!-- tag::jarModule[] -->
 75                        <jarModule>
 76                            <groupId>io.openliberty.guides</groupId>
 77                            <artifactId>guide-maven-multimodules-jar</artifactId>
 78                            <uri>/guide-maven-multimodules-jar-1.0-SNAPSHOT.jar</uri>
 79                        </jarModule>
 80                        <!-- end::jarModule[] -->
 81                        <!-- tag::webModule[] -->
 82                        <webModule>
 83                            <groupId>io.openliberty.guides</groupId>
 84                            <artifactId>guide-maven-multimodules-war</artifactId>
 85                            <uri>/guide-maven-multimodules-war-1.0-SNAPSHOT.war</uri>
 86                            <!-- Set custom context root -->
 87                            <!-- tag::contextRoot[] -->
 88                            <contextRoot>/converter</contextRoot>
 89                            <!-- end::contextRoot[] -->
 90                        </webModule>
 91                        <!-- end::webModule[] -->
 92                    </modules>
 93                </configuration>
 94            </plugin>
 95            <!-- end::maven-ear-plugin[] -->
 96
 97            <!-- Since the package type is ear,
 98            need to run testCompile to compile the tests -->
 99            <!-- tag::maven-compiler-plugin[] -->
100            <plugin>
101                <groupId>org.apache.maven.plugins</groupId>
102                <artifactId>maven-compiler-plugin</artifactId>
103                <version>3.9.0</version>
104                <executions>
105                    <execution>
106                        <phase>test-compile</phase>
107                        <!-- tag::testCompile[] -->
108                        <goals>
109                            <goal>testCompile</goal>
110                        </goals>
111                        <!-- end::testCompile[] -->
112                    </execution>
113                </executions>
114            </plugin>
115            <!-- end::maven-compiler-plugin[] -->
116
117            <!-- Plugin to run integration tests -->
118            <plugin>
119                <groupId>org.apache.maven.plugins</groupId>
120                <artifactId>maven-failsafe-plugin</artifactId>
121                <version>2.22.2</version>
122                <configuration>
123                    <systemPropertyVariables>
124                        <default.http.port>
125                            ${liberty.var.default.http.port}
126                        </default.http.port>
127                        <default.https.port>
128                            ${liberty.var.default.https.port}
129                        </default.https.port>
130                        <cf.context.root>/converter</cf.context.root>
131                    </systemPropertyVariables>
132                </configuration>
133            </plugin>
134        </plugins>
135    </build>
136
137</project>

Set the basic configuration for the project and set the packaging element to ear.

The Java library module and the web module were added as dependencies. Specify a type of war for the web module. If you don’t specify this type for the web module, Maven looks for a JAR file.

The definition and configuration of the maven-ear-plugin plug-in were added to create an EAR file. Define the jarModule and webModule modules to be packaged into the EAR file. To customize the context root of the application, set the contextRoot element to /converter in the webModule. Otherwise, Maven automatically uses the WAR file artifactId ID as the context root for the application while generating the application.xml file.

To deploy and run an EAR application on an Open Liberty server, you need to provide a server configuration file.

Create the server configuration file.
ear/src/main/liberty/config/server.xml

server.xml

 1<server description="Sample Liberty server">
 2
 3    <featureManager>
 4        <feature>pages-3.0</feature>
 5    </featureManager>
 6
 7    <variable name="default.http.port" defaultValue="9080" />
 8    <variable name="default.https.port" defaultValue="9443" />
 9
10    <!-- tag::server[] -->
11    <httpEndpoint host="*" httpPort="${default.http.port}"
12        httpsPort="${default.https.port}" id="defaultHttpEndpoint" />
13
14    <!-- tag::EARdefinition[] -->
15    <enterpriseApplication id="guide-maven-multimodules-ear"
16        location="guide-maven-multimodules-ear.ear"
17        name="guide-maven-multimodules-ear" />
18    <!-- end::EARdefinition[] -->
19    <!-- end::server[] -->
20</server>

You must configure the server.xml file with the enterpriseApplication element to specify the location of your EAR application.

Aggregating the entire build

Because you have multiple modules, aggregate the Maven projects to simplify the build process.

Create a parent pom.xml file under the start directory to link all of the child modules together. A template is provided for you.

Replace the start/POM file.
pom.xml

start/pom.xml

 1<?xml version='1.0' encoding='utf-8'?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0"
 3    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
 5    http://maven.apache.org/xsd/maven-4.0.0.xsd">
 6
 7    <modelVersion>4.0.0</modelVersion>
 8
 9    <!-- tag::packaging[] -->
10    <!-- tag::groupId[] -->
11    <groupId>io.openliberty.guides</groupId>
12    <!-- end::groupId[] -->
13    <artifactId>guide-maven-multimodules</artifactId>
14    <version>1.0-SNAPSHOT</version>
15    <!-- tag::packagingType[] -->
16    <packaging>pom</packaging>
17    <!-- end::packagingType[] -->
18    <!-- end::packaging[] -->
19
20    <!-- tag::modules[] -->
21    <modules>
22        <module>jar</module>
23        <module>war</module>
24        <module>ear</module>
25    </modules>
26    <!-- end::modules[] -->
27
28    <build>
29        <plugins>
30            <!-- Enable liberty-maven plugin -->
31            <!-- tag::liberty-maven-plugin[] -->
32            <plugin>
33                <groupId>io.openliberty.tools</groupId>
34                <artifactId>liberty-maven-plugin</artifactId>
35                <version>3.5.1</version>
36            </plugin>
37            <!-- end::liberty-maven-plugin[] -->
38        </plugins>
39    </build>
40</project>

Set the basic configuration for the project. Set pom as the value for the packaging element of the parent pom.xml file.

In the parent pom.xml file, list all of the modules that you want to aggregate for the application.

Adding the liberty-maven-plugin plug-in allows each child module to inherit the plug-in, so that you can use the Liberty Maven plug-in to develop the modules.

Developing the application

You can now develop the application and the different modules together in dev mode by using the Liberty Maven plug-in. To learn more about how to use development mode with multiple modules, check out the Documentation.

Navigate to the start directory to begin.

When you run Open Liberty in development mode, known as dev mode, the server listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your application server in dev mode is ready:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

Updating the Java classes in different modules

Update the HeightsBean class to use the Java library module that implements the functions that you need for the unit converter.

Navigate to the start directory.

Replace the HeightsBean class in the war directory.
war/src/main/java/io/openliberty/guides/multimodules/web/HeightsBean.java

HeightsBean.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.multimodules.web;
14
15public class HeightsBean implements java.io.Serializable {
16    private String heightCm = null;
17    private String heightFeet = null;
18    private String heightInches = null;
19    private int cm = 0;
20    private int feet = 0;
21    private int inches = 0;
22
23    public HeightsBean() {
24    }
25
26    // Capitalize the first letter of the name i.e. first letter after get
27    // If first letter is not capitalized, it must match the property name in
28    // index.jsp
29    public String getHeightCm() {
30        return heightCm;
31    }
32
33    public String getHeightFeet() {
34        return heightFeet;
35    }
36
37    public String getHeightInches() {
38        return heightInches;
39    }
40
41    public void setHeightCm(String heightcm) {
42        this.heightCm = heightcm;
43    }
44
45    // Need an input as placeholder, you can choose not to use the input
46    // tag::setHeightFeet[]
47    public void setHeightFeet(String heightfeet) {
48        this.cm = Integer.valueOf(heightCm);
49        // tag::getFeet[]
50        this.feet = io.openliberty.guides.multimodules.lib.Converter.getFeet(cm);
51        // end::getFeet[]
52        String result = String.valueOf(feet);
53        this.heightFeet = result;
54    }
55    // end::setHeightFeet[]
56
57    // tag::setHeightInches[]
58    public void setHeightInches(String heightinches) {
59        this.cm = Integer.valueOf(heightCm);
60        // tag::getInches[]
61        this.inches = io.openliberty.guides.multimodules.lib.Converter.getInches(cm);
62        // end::getInches[]
63        String result = String.valueOf(inches);
64        this.heightInches = result;
65    }
66    // end::setHeightInches[]
67
68}

The getFeet(cm) invocation was added to the setHeightFeet method to convert a measurement into feet.

The getInches(cm) invocation was added to the setHeightInches method to convert a measurement into inches.

You can check out the running application by going to the http://localhost:9080/converter/ URL.

Now try updating the converter so that it converts heights correctly, rather than returning 0.

Replace the Converter class in the jar directory.
jar/src/main/java/io/openliberty/guides/multimodules/lib/Converter.java

Converter.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2021 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.multimodules.lib;
14
15public class Converter {
16
17    // tag::getFeetMethod[]
18    public static int getFeet(int cm) {
19        int feet = (int) (cm / 30.48);
20        return feet;
21    }
22    // end::getFeetMethod[]
23
24    // tag::getInchesMethod[]
25    public static int getInches(int cm) {
26        double feet = cm / 30.48;
27        int inches = (int) (cm / 2.54) - ((int) feet * 12);
28        return inches;
29    }
30    // end::getInchesMethod[]
31
32    public static int sum(int a, int b) {
33        return a + b;
34    }
35
36    public static int diff(int a, int b) {
37        return a - b;
38    }
39
40    public static int product(int a, int b) {
41        return a * b;
42    }
43
44    public static int quotient(int a, int b) {
45        return a / b;
46    }
47
48}

Change the getFeet method so that it converts from centimetres to feet, and the getInches method so that it converts from centimetres to inches. Update the sum, diff, product and quotient functions so that they add, subtract, multiply, and divide 2 numbers respectively.

Now revisit the application at the http://localhost:9080/converter/ URL. Try entering a height in centimetres and see if it converts correctly.

Testing the multi-module application

To test the multi-module application, add integration tests to the EAR project.

Create the integration test class in the ear directory.
ear/src/test/java/it/io/openliberty/guides/multimodules/IT.java

IT.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package it.io.openliberty.guides.multimodules;
14
15import static org.junit.jupiter.api.Assertions.assertEquals;
16import static org.junit.jupiter.api.Assertions.assertTrue;
17
18import java.io.BufferedReader;
19import java.io.InputStreamReader;
20import java.net.HttpURLConnection;
21import java.net.URL;
22
23import org.junit.jupiter.api.Test;
24
25public class IT {
26    String port = System.getProperty("default.http.port");
27    String war = "converter";
28    String urlBase = "http://localhost:" + port + "/" + war + "/";
29
30    @Test
31    // tag::testIndexPage[]
32    public void testIndexPage() throws Exception {
33        String url = this.urlBase;
34        HttpURLConnection con = testRequestHelper(url, "GET");
35        assertEquals(200, con.getResponseCode(), "Incorrect response code from " + url);
36        assertTrue(testBufferHelper(con).contains("Enter the height in centimeters"),
37                        "Incorrect response from " + url);
38    }
39    // end::testIndexPage[]
40
41    @Test
42    // tag::testHeightsPage[]
43    public void testHeightsPage() throws Exception {
44        String url = this.urlBase + "heights.jsp?heightCm=10";
45        HttpURLConnection con = testRequestHelper(url, "POST");
46        assertTrue(testBufferHelper(con).contains("3        inches"),
47                        "Incorrect response from " + url);
48    }
49    // end::testHeightsPage[]
50
51    private HttpURLConnection testRequestHelper(String url, String method)
52                    throws Exception {
53        URL obj = new URL(url);
54        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
55        // optional default is GET
56        con.setRequestMethod(method);
57        return con;
58    }
59
60    private String testBufferHelper(HttpURLConnection con) throws Exception {
61        BufferedReader in = new BufferedReader(
62                        new InputStreamReader(con.getInputStream()));
63        String inputLine;
64        StringBuffer response = new StringBuffer();
65        while ((inputLine = in.readLine()) != null) {
66            response.append(inputLine);
67        }
68        in.close();
69        return response.toString();
70    }
71
72}

The testIndexPage tests to check that you can access the landing page.

The testHeightsPage tests to check that the application can process the input value and calculate the result correctly.

Running the tests

Because you started Open Liberty in development mode, press the enter/return key to run the tests.

You will see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.multimodules.IT
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.712 sec - in it.io.openliberty.guides.multimodules.IT

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

When you are done checking out the service, exit development mode by pressing CTRL+C in the command-line session where you ran the server, or by typing q and then pressing the enter/return key.

Building the multi-module application

You aggregated and developed the application. Now, you can run mvn install once from the start directory and it will automatically build all your modules. This command creates a JAR file in the jar/target directory, a WAR file in the war/target directory, and an EAR file that contains the JAR and WAR files in the ear/target directory.

Run the following command from the start directory to build the entire application:

mvn install

Since the modules are independent, you can re-build them individually by running mvn install from the corresponding start directory for each module.

Or, run mvn -pl <child project> install from the start directory.

Great work! You’re done!

You built and tested a multi-module Java application for unit conversion with Maven on Open Liberty.

Guide Attribution

Creating a multi-module application by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star