Split your application into modules
Turn my-app into a parent POM with a library module and an application module, build them together, and build one with what it needs.
- Holds for
- 3.93.104.0
- Changed
- Kind
- Tutorial
- Source
- Edit this page
- Wrong?
- Report a problem
In this tutorial you split my-app into two modules: greeting, a library with the Greeter class and its test, and app, the application that uses it and is packed into one JAR. The top directory gets a parent POM that holds what the modules share and builds them together.
Start in the my-app directory as you left it at the end of Test and build your project. At the end of this tutorial it looks like this:
my-app
|-- .mvn
|-- pom.xml
|-- app
| |-- pom.xml
| `-- src/main/java/com/mycompany/app/App.java
`-- greeting
|-- pom.xml
`-- src
|-- main/java/com/mycompany/app/Greeter.java
`-- test/java/com/mycompany/app/GreeterTest.java
1. Move the code into module directories
Remove the output of earlier builds first, while pom.xml still describes the old project:
mvn clean
Then make a directory for each module, move all code into app, and move Greeter and its test on to greeting:
mkdir -p app greeting/src/main/java/com/mycompany/app
mv src app/
mv app/src/test greeting/src/
mv app/src/main/java/com/mycompany/app/Greeter.java greeting/src/main/java/com/mycompany/app/
Each module has the standard directory layout of its own. The .mvn directory stays at the top: it marks the top directory of the whole project.
2. Turn the top POM into a parent
In pom.xml in the my-app directory, add <packaging>pom</packaging> after the version and a <modules> list after <url>. Remove the Commons Lang dependency and the <plugins> element with the Shade plugin; they move to the modules. The file now reads:
pom.xml
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>my-app</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<modules>
<module>greeting</module>
<module>app</module>
</modules>
<properties>
...
</properties>
<dependencyManagement>
...
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
...
</dependencies>
<build>
<pluginManagement>
...
</pluginManagement>
</build>
</project>
Packaging pom means that this project builds no JAR of its own. <modules> lists the directories of the modules, and makes Maven build them whenever you build this project.
This POM is also the parent of both modules. They inherit its groupId and version, its properties such as maven.compiler.release, the plugin versions in <pluginManagement> and the JUnit dependencies, so either module can have tests.
3. Write the POM of the library
Create a POM for greeting. It names my-app as its parent and adds the dependency its code needs:
greeting/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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>greeting</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.21.0</version>
</dependency>
</dependencies>
</project>
The module only declares its artifactId; the rest of its coordinates come from the parent. Maven finds the parent's POM in the directory above, where it looks first.
4. Write the POM of the application
Create a POM for app. It depends on greeting and gets the Shade plugin from Add and configure your first plugin:
app/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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>app</artifactId>
<dependencies>
<dependency>
<groupId>com.mycompany.app</groupId>
<artifactId>greeting</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.mycompany.app.App</mainClass>
</transformer>
</transformers>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
A module uses another module through an ordinary dependency on its coordinates. ${project.version} is the version of this project, so app always uses the greeting of the same version. app gets Commons Lang through greeting, without declaring it.
Maven 4: With model version 4.1.0, which only Maven 4 reads, the POMs can be shorter. The parent lists
<subprojects>with one<subproject>per directory instead of<modules>, a module names its parent with an empty<parent/>, andappcan leave out the<version>ofgreeting:app/pom.xml
<project xmlns="http://maven.apache.org/POM/4.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.1.0 https://maven.apache.org/xsd/maven-4.1.0.xsd"> <modelVersion>4.1.0</modelVersion> <parent/> <artifactId>app</artifactId> <dependencies> <dependency> <groupId>com.mycompany.app</groupId> <artifactId>greeting</artifactId> </dependency> </dependencies> ... </project>Maven 3 stops on such a POM with
Unrecognised tag: 'subprojects'. Split a project into modules shows the parent POM in this form.
5. Build all modules
In the my-app directory, run:
mvn clean verify
Maven reads the parent POM, finds the modules, and puts all three projects in an order in which every project comes after the projects it needs. It prints that order first:
[INFO] Reactor Build Order:
[INFO]
[INFO] my-app [pom]
[INFO] greeting [jar]
[INFO] app [jar]
Then it builds them one after the other, in one run: it tests greeting and makes its JAR, and then compiles app against that JAR and packs both, with Commons Lang, into one. The summary at the end lists each project:
[INFO] Reactor Summary for my-app 1.0-SNAPSHOT:
[INFO]
[INFO] my-app ............................................. SUCCESS [ 0.510 s]
[INFO] greeting ........................................... SUCCESS [ 1.180 s]
[INFO] app ................................................ SUCCESS [ 0.256 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
The part of Maven that builds several projects together is called the reactor. Each module has its own target directory. Run the application from the JAR of app:
java -jar app/target/app-1.0-SNAPSHOT.jar ada
Hello, Ada!
6. Build one module and what it needs
You can build part of the project from the top directory with -pl, short for --projects, followed by the module's directory. Start from nothing and try app alone:
mvn clean
mvn -pl app verify
[ERROR] Failed to execute goal on project app: Could not resolve dependencies for project com.mycompany.app:app:jar:1.0-SNAPSHOT
[ERROR] dependency: com.mycompany.app:greeting:jar:1.0-SNAPSHOT (compile)
greeting is not part of this build, so Maven looks for it in the local repository, as for any other dependency, and you never installed it. The line after these two gives a reason that differs between Maven versions: Maven 3.10 and 4 say the path is NOT allowed from central, which only means that Maven Central has no such library.
Add -am, short for --also-make, to build the module together with the modules it needs:
mvn -pl app -am verify
[INFO] Reactor Summary for my-app 1.0-SNAPSHOT:
[INFO]
[INFO] my-app ............................................. SUCCESS [ 0.001 s]
[INFO] greeting ........................................... SUCCESS [ 1.695 s]
[INFO] app ................................................ SUCCESS [ 0.273 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
Maven builds greeting because app depends on it, and the parent my-app because app inherits from it. Here that is the whole project; in a larger project, -am leaves out the modules that app does not need. Build part of a multi-module project shows the other ways to select modules.
Maven 4: When Maven 4 packages a module, it keeps a copy of the JAR in
.mvn/target/project-local-repo. After a fullmvn verify,mvn -pl app verifyfindsgreetingthere and succeeds;mvn cleanremoves the copies, so the build above fails on Maven 4 as well.
You now have a project of two modules that build together with one command, and you can build one of them with what it needs.
Where to go from here
Aggregation and inheritance explains the two roles the parent POM plays, and Split a project into modules is the same work as a checklist for your own projects. The rest of the Docs section is grouped by subject: Dependencies and repositories, Multi-module projects, Java versions and toolchains, Plugins, extensions and tools, and Build recipes, each starting from what you did in these five tutorials.