Maven multimodule project notes

Russell Bateman
July 2023
last update:


I'm finally getting around to dropping some notes on this confusing topic. Below is the context of my project and several solutions to follow here to accomplish several things:

  1. The project is an application for performing extract, transfer and load of medical data.

  2. There is a project root and three submodules.

  3. Telescopically, one module, base, supplies dependencies to feeder, then, base and feeder supply dependencies to medical-filter. Each of these submodules becomes a temporary JAR during the Maven build process (thwarted, of course, by any compilation errors, JUnit test errors, etc.).

  4. There is a lib subdirectory at the root containing local, static Maven repository artifacts, i.e.: just like a local Maven repository. This is because there are static JARs that cannot be found out there in the world; they only exist as files in our hands. The pom.xml files are doctored to make use of this static repository.

  5. The project root pom.xml is rigged to perform
    1. Definition of all Maven properties to be used everywhere (throughout the child pom.xml files).
    2. Maven Dependency Management.
    3. Child modules will only have to define which Maven dependencies they want without caring about versions.
    4. Definition of all dependencies and their versions (in one place).
    5. The static, local Maven repository is caught up in this too (the lib subdirectory).
etl
├── lib
├── base
│   └── pom.xml
├── feeder
│   └── pom.xml
├── medical-filter
│   └── pom.xml
└── pom.xml

In short, this is a demonstration for copy-and-paste to set up (I'm using IntelliJ IDEA) a project's pom.xml files which are usefully cribbed here for the hardest things to get right are the first four lines, the <parent ../> clause, the <repositories ... /> clause (for making use of static JARs in a Maven project) and, for Maven Dependency Management, the relationship between what goes in the root pom.xml and what goes in the child pom.xml files.

The base module's pom.xml file is our "lowest-level" submodule on which its siblings depend. Its defined dependencies are filled out with examples (still only a tiny subset) than appear in the two other sibling pom.xml files. XML comments describe the "blanks" that need to be filled in.

root/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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <artifactId>etl</artifactId>
  <groupId>com.acme.etl</groupId>
  <version>3.0.0</version>
  <packaging>pom</packaging>
  <name>Acme Corporation ETL Application</name>

  <modules>
    <module>base</module>
    <module>feeder</module>
    <module>medical-filter</module>
  </modules>

  <properties>

    <!-- properties like these: -->
    <maven-antrun-plugin.version>3.0.0</maven-antrun-plugin.version>
    <maven.compiler.plugin.version>3.10.1</maven.compiler.plugin.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.install.skip>true</maven.install.skip>
    <maven.deploy.skip>true</maven.deploy.skip>
    <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
    <maven-dependency-plugin.version>3.5.0</maven-dependency-plugin.version>

    <!-- and these which will help subordinate pom.xml files: -->
    <logback.version>1.2.10</logback.version>
    <slf4j.version>[1.7.25]</slf4j.version>
    <mockito.version>1.8.4</mockito.version>
    <junit.version>4.12</junit.version>

  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.acme.base</groupId>
        <artifactId>base</artifactId>
        <version>3.0.0</version>
      </dependency>
      <dependency>
        <groupId>com.acme.feeder</groupId>
        <artifactId>feeder</artifactId>
        <version>3.0.0</version>
      </dependency>
      <dependency>
        <groupId>com.acme.filter</groupId>
        <artifactId>medical-filter</artifactId>
        <version>3.0.0</version>
      </dependency>

      <dependency>
        <!-- dependencies with version numbers -->
      </dependency>

    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven.compiler.plugin.version}</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <!-- see Maven property (macros/variables) values! -->
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>${maven-antrun-plugin.version}</version>
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <target>
                <echoproperties />
              </target>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
base/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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme</groupId>
  <artifactId>base</artifactId>
  <packaging>jar</packaging>

  <name>Acme Corporation ETL base software</name>

  <parent>
    <groupId>com.acme.etl</groupId>
    <artifactId>etlr</artifactId>
    <version>3.0.0</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <properties>
    <code.root>../..</code.root>
  </properties>

  <repositories>
    <repository>
      <!-- These are local, static Maven libraries. -->
      <id>local libraries (static)</id>
      <name>local-static</name>
      <url>file://${project.basedir}/lib</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <!-- dependencies (mostly) without version numbers, e.g.: -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-access</artifactId>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>

    </dependency>
  </dependencies>

  <build>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven.compiler.plugin.version}</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>${maven-dependency-plugin.version}</version>
      </plugin>
    </plugins>
  </build>
</project>
feeder/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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme</groupId>
  <artifactId>feeder</artifactId>
  <packaging>jar</packaging>

  <name>Acme Corporation ETL feeder software</name>

  <parent>
    <groupId>com.acme.etl</groupId>
    <artifactId>etl</artifactId>
    <version>3.0.0</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <properties>
    <code.root>../..</code.root>
  </properties>

  <repositories>
    <repository>
      <!-- These are local, static Maven libraries. -->
      <id>local libraries (static)</id>
      <name>local-static</name>
      <url>file://${project.basedir}/lib</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>com.acme</groupId>
      <artifactId>base</artifactId>
      <version>${project.version}</version>
    </dependency>

    <dependency>
      <!-- dependencies (mostly) without version numbers -->
    </dependency>

   </dependencies>

  <build>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven.compiler.plugin.version}</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>${maven-dependency-plugin.version}</version>
      </plugin>
    </plugins>
  </build>
</project>
medical-filter/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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme</groupId>
  <artifactId>medical-filter</artifactId>
  <packaging>jar</packaging>

  <name>Acme Corporation ETL medical-filter software</name>

  <parent>
    <groupId>com.acme.etl</groupId>
    <artifactId>etl</artifactId>
    <version>3.0.0</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <properties>
    <code.root>../..</code.root>
  </properties>

  <repositories>
    <repository>
      <!-- These are local, static Maven libraries. -->
      <id>local libraries (static)</id>
      <name>local-static</name>
      <url>file://${project.basedir}/lib</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>com.acme</groupId>
      <artifactId>feeder</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>com.acme</groupId>
      <artifactId>base</artifactId>
      <version>${project.version}</version>
    </dependency>

    <dependency>
      <!-- dependencies (mostly) without version numbers -->
    </dependency>

   </dependencies>

  <build>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven.compiler.plugin.version}</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>${maven-dependency-plugin.version}</version>
      </plugin>
    </plugins>
  </build>
</project>