git clone https://github.com/openliberty/guide-gradle-multimodules.git
cd guide-gradle-multimodules
Creating a multi-module application with Gradle
Prerequisites:
You will learn how to build an application with multiple modules with Gradle and Open Liberty.
What you’ll learn
A 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 Gradle 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 the Liberty Gradle plug-in to develop a multi-module application in dev mode without having to prebuild the JAR and WAR files. In dev mode, your changes are automatically picked up by the running Liberty instance.
You will build a unit converter application that converts heights from centimeters into feet and inches. The application prompts 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:
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.
You can access a 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:
-
Add a dependency relationship between the two modules.
-
Assemble the entire application into an EAR file.
-
Aggregate the entire build.
-
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 Gradle task to build the application:
cd finish ./gradlew libertyPackage
To deploy your EAR application on Open Liberty, run the Gradle libertyRun
task from the finish
directory.
./gradlew libertyRun
After you see the following message, your Liberty instance is ready:
The sampleLibertyServer server is ready to run a smarter planet.
When the Liberty instance is running, you can find the application at the following URL: http://localhost:9080/converter/
After you finish checking out the application, stop the Open Liberty instance by pressing CTRL+C in the command-line session where you ran Liberty. Alternatively, you can run the libertyStop
task from the finish
directory in another command-line session:
./gradlew libertyStop
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 build.gradle
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/build.gradle file.
war/build.gradle
war/build.gradle
1apply plugin: 'war'
2
3description = 'WAR Module'
4
5dependencies {
6 // tag::projectJar[]
7 implementation project(':jar')
8 // end::projectJar[]
9 compileOnly 'jakarta.platform:jakarta.jakartaee-api:10.0.0'
10 compileOnly 'org.eclipse.microprofile:microprofile:7.0'
11}
12
13war {
14 archiveFileName = rootProject.name + '-' + getArchiveBaseName().get() + '-' +
15 rootProject.version + '.' + getArchiveExtension().get()
16}
17
18// tag::dependsOn[]
19war.dependsOn ':jar:jar'
20// end::dependsOn[]
The added project
dependency and dependsOn
element declare the Java library project and module that implements the functions that you need for the unit converter.
Assembling multiple modules into an EAR file
To deploy the entire application on Open Liberty, 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 build.gradle
file.
Replace the ear/build.gradle file.
ear/build.gradle
ear/build.gradle
1// tag::ear[]
2apply plugin: 'ear'
3// end::ear[]
4// tag::liberty[]
5apply plugin: 'liberty'
6// end::liberty[]
7
8description = 'EAR Module'
9
10buildscript {
11 repositories {
12 mavenLocal()
13 mavenCentral()
14 maven {
15 name = 'Sonatype Nexus Snapshots'
16 url = 'https://oss.sonatype.org/content/repositories/snapshots/'
17 }
18 }
19 dependencies {
20 classpath 'io.openliberty.tools:liberty-gradle-plugin:3.9.4'
21 }
22}
23
24
25dependencies {
26 // tag::war[]
27 deploy project(path:':war', configuration:'archives')
28 // end::war[]
29}
30
31// tag::earConfig[]
32ear {
33 archiveFileName = rootProject.name + '-' + getArchiveBaseName().get() + '-' +
34 rootProject.version + '.' + getArchiveExtension().get()
35 deploymentDescriptor {
36 // tag::webModule[]
37 webModule ('guide-gradle-multimodules-war-1.0-SNAPSHOT.war', '/converter')
38 // end::webModule[]
39 }
40}
41// end::earConfig[]
42
43// tag::libertyConfig[]
44liberty {
45 server {
46 // tag::sampleLibertyServer[]
47 name = 'sampleLibertyServer'
48 // end::sampleLibertyServer[]
49 deploy {
50 apps = [ear]
51 // tag::copyLibsDirectory[]
52 copyLibsDirectory = file("${project.getLayout().getBuildDirectory().getAsFile().get()}/libs")
53 // end::copyLibsDirectory[]
54 }
55 // tag::httpPort[]
56 var.'http.port' = '9080'
57 // end::httpPort[]
58 var.'https.port' = '9443'
59 verifyAppStartTimeout = 30
60 looseApplication = true
61 }
62}
63// end::libertyConfig[]
64
65// tag::test[]
66test {
67 systemProperty 'http.port', liberty.server.var.'http.port'
68 enabled = gradle.startParameter.taskNames.contains('test')
69}
70// end::test[]
71
72// tag::deployDependsOn[]
73deploy.dependsOn 'ear'
74// end::deployDependsOn[]
75// tag::earDependsOn[]
76ear.dependsOn ':jar:jar', ':war:war'
77// end::earDependsOn[]
Let’s look at what each section does.
The first two lines specify that you want to use the ear
and liberty
plug-ins for Gradle. The ear
section configures the ear
task with the deployment descriptor that provides the web module file name and the context root as /converter
.
If no context path is specified, Gradle automatically uses the WAR file artifact ID as the context root for the application, when the application.xml
file is being generated. The default artifact ID is {projectName}-{moduleName}-{version}
, like guide-gradle-multimodules-war-1.0-SNAPSHOT
.
The liberty
section configures the liberty
task that creates the Liberty server name as sampleLibertyServer
and deploys the ear file from the path that is specified by the copyLibsDirectory
variable.
The war
project is added as a project dependency. The deploy
task depends on the ear
task and the ear
task depends on the jar
task from the jar
project and the war
task from the war
project.
To deploy and run an EAR application on an Open Liberty instance, you need to provide a Liberty server.xml
configuration file.
Create the Libertyserver.xml
configuration file.ear/src/main/liberty/config/server.xml
server.xml
1<server description="Sample Liberty server">
2
3 <featureManager>
4 <platform>jakartaee-10.0</platform>
5 <feature>pages</feature>
6 </featureManager>
7
8 <variable name="http.port" defaultValue="9080" />
9 <variable name="https.port" defaultValue="9443" />
10
11 <!-- tag::server[] -->
12 <httpEndpoint host="*" httpPort="${http.port}"
13 httpsPort="${https.port}" id="defaultHttpEndpoint" />
14
15 <!-- tag::EARdefinition[] -->
16 <enterpriseApplication id="guide-gradle-multimodules-ear"
17 location="guide-gradle-multimodules-ear-1.0-SNAPSHOT.ear"
18 name="guide-gradle-multimodules-ear">
19 </enterpriseApplication>
20 <!-- end::EARdefinition[] -->
21 <!-- end::server[] -->
22</server>
The server.xml
configuration file configures with the enterpriseApplication
element to specify the location of your EAR application.
Aggregating the entire build
Because you have multiple modules, aggregate the Gradle projects to simplify the build process.
Replace the settings.gradle file.
settings.gradle
settings.gradle
1rootProject.name = 'guide-gradle-multimodules'
2// tag::includeJar[]
3include ':jar'
4// end::includeJar[]
5// tag::includeWar[]
6include ':war'
7// end::includeWar[]
8// tag::includeEar[]
9include ':ear'
10// end::includeEar[]
11
12// tag::jarDir[]
13project(':jar').projectDir = "$rootDir/jar" as File
14// end::jarDir[]
15// tag::warDir[]
16project(':war').projectDir = "$rootDir/war" as File
17// end::warDir[]
18// tag::earDir[]
19project(':ear').projectDir = "$rootDir/ear" as File
20// end::earDir[]
The settings.gradle
file is used to specify multiple modules that includes the jar
, war
, and ear
projects and their directories.
Create a parent build.gradle
file under the start
directory to link all of the child modules together. A template is provided for you.
Replace the build.gradle file.
build.gradle
build.gradle
1// tag::allprojects[]
2allprojects {
3 group = 'io.openliberty.guides'
4 version = '1.0-SNAPSHOT'
5}
6// end::allprojects[]
7
8subprojects {
9 // tag::java[]
10 apply plugin: 'java'
11 // end::java[]
12
13 // tag::options[]
14 java {
15 sourceCompatibility = JavaVersion.VERSION_11
16 targetCompatibility = JavaVersion.VERSION_11
17 }
18
19 tasks.withType(JavaCompile).configureEach {
20 options.encoding = 'UTF-8'
21 options.release.set(11)
22 }
23 // end::options[]
24
25 // tag::junit[]
26 test {
27 useJUnitPlatform()
28 }
29
30 dependencies {
31 testImplementation platform('org.junit:junit-bom:5.13.4')
32 testImplementation 'org.junit.jupiter:junit-jupiter'
33 testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
34 }
35 // end::junit[]
36
37 // tag::repositories[]
38 repositories {
39 mavenLocal()
40 mavenCentral()
41 }
42 // end::repositories[]
43
44}
The allprojects
task sets the basic configuration for the project. The subprojects
task applies the java
plug-in with its options
and junit
dependencies to all subprojects. The plug-ins and dependencies will be downloaded from the repositories that are specified in the repositories
configuration. Each child module inherits all the configurations.
Developing the application
You can now develop the application and the different modules together in dev mode by using the Liberty Gradle plug-in. To learn more about how to use dev mode with multiple modules, check out the Documentation.
When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following task to start Open Liberty in dev mode:
./gradlew libertyDev
After you see the following message, your Liberty instance is ready in dev mode:
************************************************************** * 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
To get the height conversion working correctly in the application, you’ll need to update two Java classes: one in the web module and one in the library module.
First, update the HeightsBean
class to use the Java library module that implements the functions that you need for the unit converter.
Replace theHeightsBean
class in thewar
directory.war/src/main/java/io/openliberty/guides/multimodules/web/HeightsBean.java
HeightsBean.java
The getFeet(cm)
invocation is added to the setHeightFeet
method to convert a measurement into feet.
The getInches(cm)
invocation is 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.
Note that the application currently returns 0 for height conversions because the logic in the converter hasn’t been implemented yet. You’ll fix this by updating the converter in the following step.
Replace theConverter
class in thejar
directory.jar/src/main/java/io/openliberty/guides/multimodules/lib/Converter.java
Converter.java
The getFeet
method is changed to convert the cm
integer parameter from centimeters to feet, and the getInches
method to convert the cm
integer parameter from centimeters to inches. The sum
, diff
, product
, and quotient
methods are updated to add, subtract, multiply, and divide 2 numbers respectively.
Now check out the application again at the http://localhost:9080/converter/ URL.
Try entering a height in centimeters 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 theear
directory.ear/src/test/java/it/io/openliberty/guides/multimodules/IT.java
IT.java
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.
ear/build.gradle
1// tag::ear[]
2apply plugin: 'ear'
3// end::ear[]
4// tag::liberty[]
5apply plugin: 'liberty'
6// end::liberty[]
7
8description = 'EAR Module'
9
10buildscript {
11 repositories {
12 mavenLocal()
13 mavenCentral()
14 maven {
15 name = 'Sonatype Nexus Snapshots'
16 url = 'https://oss.sonatype.org/content/repositories/snapshots/'
17 }
18 }
19 dependencies {
20 classpath 'io.openliberty.tools:liberty-gradle-plugin:3.9.4'
21 }
22}
23
24
25dependencies {
26 // tag::war[]
27 deploy project(path:':war', configuration:'archives')
28 // end::war[]
29}
30
31// tag::earConfig[]
32ear {
33 archiveFileName = rootProject.name + '-' + getArchiveBaseName().get() + '-' +
34 rootProject.version + '.' + getArchiveExtension().get()
35 deploymentDescriptor {
36 // tag::webModule[]
37 webModule ('guide-gradle-multimodules-war-1.0-SNAPSHOT.war', '/converter')
38 // end::webModule[]
39 }
40}
41// end::earConfig[]
42
43// tag::libertyConfig[]
44liberty {
45 server {
46 // tag::sampleLibertyServer[]
47 name = 'sampleLibertyServer'
48 // end::sampleLibertyServer[]
49 deploy {
50 apps = [ear]
51 // tag::copyLibsDirectory[]
52 copyLibsDirectory = file("${project.getLayout().getBuildDirectory().getAsFile().get()}/libs")
53 // end::copyLibsDirectory[]
54 }
55 // tag::httpPort[]
56 var.'http.port' = '9080'
57 // end::httpPort[]
58 var.'https.port' = '9443'
59 verifyAppStartTimeout = 30
60 looseApplication = true
61 }
62}
63// end::libertyConfig[]
64
65// tag::test[]
66test {
67 systemProperty 'http.port', liberty.server.var.'http.port'
68 enabled = gradle.startParameter.taskNames.contains('test')
69}
70// end::test[]
71
72// tag::deployDependsOn[]
73deploy.dependsOn 'ear'
74// end::deployDependsOn[]
75// tag::earDependsOn[]
76ear.dependsOn ':jar:jar', ':war:war'
77// end::earDependsOn[]
The test
task configuration is already included in the ear/build.gradle
file for you. It passes the same http.port
value that is used by the Liberty server to the tests so they can connect to the application during execution.
Running the tests
Because you started Open Liberty in dev mode, press the enter/return key to run the tests.
You will see the following output:
Running tests...
> Task :ear:cleanTest
> Task :jar:cleanTest
> Task :war:cleanTest
> Task :ear:compileJava NO-SOURCE
> Task :ear:processResources NO-SOURCE
> Task :ear:classes UP-TO-DATE
> Task :ear:compileTestJava UP-TO-DATE
> Task :ear:processTestResources NO-SOURCE
> Task :ear:testClasses UP-TO-DATE
> Task :ear:test
> Task :jar:compileJava UP-TO-DATE
> Task :jar:processResources NO-SOURCE
> Task :jar:classes UP-TO-DATE
> Task :jar:jar UP-TO-DATE
> Task :jar:compileTestJava UP-TO-DATE
> Task :jar:processTestResources NO-SOURCE
> Task :jar:testClasses UP-TO-DATE
> Task :jar:test
> Task :war:compileJava UP-TO-DATE
> Task :war:processResources NO-SOURCE
> Task :war:classes UP-TO-DATE
> Task :war:compileTestJava UP-TO-DATE
> Task :war:processTestResources NO-SOURCE
> Task :war:testClasses UP-TO-DATE
> Task :war:test
BUILD SUCCESSFUL in 3s
12 actionable tasks: 6 executed, 6 up-to-date
> Task :ear:libertyDev
Tests finished.
You can find the test result of each module from their build directory:
-
jar/build/reports/tests/test/index.html
-
ear/build/reports/tests/test/index.html
-
war/build/reports/tests/test/index.html
When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty.
Building the multi-module application
You aggregated and developed the application. Now, you can run ./gradlew clean libertyPackage
from the start
directory to build all your modules. This command creates a JAR file in the jar/build/libs
directory, a WAR file in the war/build/libs
directory, and an EAR file that contains the WAR file in the ear/build/libs
directory.
Run the following command from the start
directory to build the entire application:
./gradlew clean libertyPackage
Because the modules are independent, you can rebuild them individually by running ./gradlew clean build
from the corresponding start
directory for each module.
Or, run ./gradlew <child project>:build
from the start
directory.
Great work! You’re done!
You built and tested a multi-module Java application for unit conversion with Gradle on Open Liberty.
Guide Attribution
Creating a multi-module application with Gradle by Open Liberty is licensed under CC BY-ND 4.0
Prerequisites:
Great work! You're done!
What did you think of this guide?




Thank you for your feedback!
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