Maven (Part 9) - Introduction to POM
Maven (Part 9) - Introduction to POM

Maven (Part 9) - Introduction to POM

in
  1. What is POM
  2. Super POM
  3. Minimal POM
  4. Project Inheritance
    1. Example 1
      1. Scenario
      2. Solution
    2. Example 2
      1. Scenario
      2. Solution
  5. Project Aggregation
    1. Example 3
      1. Scenario
      2. Solution
    2. Example 4
      1. Scenario
      2. Solution
  6. Comparison between Inheritance and Aggregation
    1. Example 5
      1. Scenario
      2. Solution
  7. Project Interpolation and Variables
    1. Available Variables
      1. Project Model Variables
      2. Special Variables

What is POM

The Project Object Model, or POM, serves as the cornerstone of Maven. It’s an XML file containing project information and detailed configurations for Maven to build the project. It encapsulates default values for most project parameters. For instance, the build directory defaults to target, source code resides in src/main/java, test source code in src/test/java, and so forth. When tasks or goals are executed, Maven looks for the POM in the current directory. It reads the POM, extracts the required configuration, and proceeds with the goals.

Configurations that can be specified within the POM include project dependencies, executable plugins or goals, build profiles, and more. Additionally, project version, description, developers, mailing lists, among other details, can be specified.

Super POM

The Super POM represents Maven’s default POM. Unless explicitly overridden, all POMs extend the Super POM, implying that any POM you create for your project inherits configurations specified in the Super POM.

You can explore the Super POM for Maven 3.6.3 in the Maven Core Reference Documentation.

Minimal POM

The Minimal POM represents the bare minimum requirements for a POM. Its simplest structure includes:

  • project - the project root
  • modelVersion - should be set to 4.0.0
  • groupId - the ID of the project group
  • artifactId - the ID of the artifact (project)
  • version - the version of the project within the group

The pom.xml looks like this:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
</project>

The POM mandates the configuration of groupId, artifactId, and version. These three values form the fully qualified artifact name of the project, structured as <groupId>:<artifactId>:<version>. In the example above, the fully qualified artifact name is com.mycompany.app:my-app:1.

As mentioned earlier, Maven utilizes default values when specific configurations are not provided. One such default is the packaging type. Each Maven project has a packaging type, defaulting to jar if not specified in the POM.

Notably, the Minimal POM does not specify repositories. If building a project with the Minimal POM, it inherits repository configurations from the Super POM. Thus, when Maven encounters dependencies in the Minimal POM, it knows to download them from the repository specified in the Super POM, typically https://repo.maven.apache.org/maven2.

Project Inheritance

POM elements that can be inherited include:

  • dependencies
  • developers and contributors
  • plugin lists (including reports)
  • plugin executions with matching ids
  • plugin configuration
  • resources

While the Super POM serves as an example of project inheritance, you can introduce your own parent POM by specifying the parent element within your POM, as illustrated in the following examples.

Example 1

Scenario

Consider reusing the previous artifact com.mycompany.app:my-app:1 and introducing another artifact com.mycompany.app:my-module:1.

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-module</artifactId>
  <version>1</version>
</project>

Let’s specify their directory structure as follows:

.
 |-- my-module
 |   `-- pom.xml
 `-- pom.xml

Note: my-module/pom.xml is the POM for com.mycompany.app:my-module:1, while pom.xml is the POM for com.mycompany.app:my-app:1.

Solution

Now, to make com.mycompany.app:my-app:1 the parent artifact for com.mycompany.app:my-module:1, we need to modify the POM for com.mycompany.app:my-module:1 as follows:

POM for com.mycompany.app:my-module:1

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
  </parent>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-module</artifactId>
  <version>1</version>
</project>

Note that we’ve added a new section, namely the parent section. This section allows us to specify which artifact serves as the parent for the POM. To do so, we need to specify the fully qualified artifact name of the parent POM. With these settings, our module can inherit certain properties from the parent POM.

Additionally, if you wish the module’s groupId or version to match the parent module, you can omit the groupId or version tags in the module’s POM:

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
  </parent>
 
  <artifactId>my-module</artifactId>
</project>

This allows the module to inherit the groupId and version from its parent POM.

Example 2

Scenario

However, this approach is feasible only if the parent project is already installed in the local repository or structured in a specific directory hierarchy (i.e., the parent pom.xml is one level above the module’s pom.xml).

But what if the parent project is not yet installed and the directory structure is as follows?

.
 |-- my-module
 |   `-- pom.xml
 `-- parent
     `-- pom.xml

Solution

To handle such directory structures (or any other structure), we must add a <relativePath> element in the parent section.

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
    <relativePath>../parent/pom.xml</relativePath>
  </parent>
 
  <artifactId>my-module</artifactId>
</project>

As the name suggests, it’s the relative path from the module’s `pom

.xml to the parent's pom.xml`.

Project Aggregation

Project aggregation, similar to project inheritance, involves specifying modules from the parent POM rather than specifying a parent POM from the module. Thus, the parent project knows about its modules, and Maven commands executed for the parent project also apply to its modules. To aggregate projects, you need to:

  • Change the <packaging> value in the parent POM to pom.
  • Specify the directories of its modules in the parent POM.

Example 3

Scenario

The original POM and directory structure are as follows:

POM for com.mycompany.app:my-app:1

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
</project>

POM for com.mycompany.app:my-module:1

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-module</artifactId>
  <version>1</version>
</project>

Directory Structure

.
 |-- my-module
 |   `-- pom.xml
 `-- pom.xml

Solution

To aggregate my-module into my-app, we simply modify my-app:

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
  <packaging>pom</packaging>
 
  <modules>
    <module>my-module</module>
  </modules>
</project>

In the modified com.mycompany.app:my-app:1, we’ve added the packaging section and the modules section. The packaging is set to pom, and the modules section includes the element <module>my-module</module>. The value of <module> is the relative path from com.mycompany.app:my-app:1 to com.mycompany.app:my-module:1’s POM (following convention, we use the artifactId of the module as the directory name).

Now, whenever Maven commands are executed for com.mycompany.app:my-app:1, they’ll also be applied to com.mycompany.app:my-module:1. Additionally, certain commands (especially goals) handle project aggregation differently.

Example 4

Scenario

But what if we change the directory structure to the following?

.
 |-- my-module
 |   `-- pom.xml
 `-- parent
     `-- pom.xml

How does the parent POM specify the module?

Solution

The solution is similar to Example 3, specifying the module’s path:

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
  <packaging>pom</packaging>
 
  <modules>
    <module>../my-module</module>
  </modules>
</project>

Comparison between Inheritance and Aggregation

Inheritance: If you have multiple Maven projects with similar configurations, you can refactor your projects by extracting these common configurations and creating a parent project. Then, by making your Maven projects inherit from this parent project, these configurations will be applied across all projects.

Aggregation: If you have a set of projects that are built or handled together, you can create a parent project and declare these projects as its modules. Consequently, by building the parent project, the other projects will follow suit.

Using Inheritance and Aggregation together: Of course, you can also use project inheritance and project aggregation simultaneously. In other words, you can specify a parent project for your modules while also specifying those Maven projects as modules for the parent project. You just need to apply these three rules:

  • Specify the parent POM in each child POM.
  • Change the <packaging> value of the parent POM to pom.
  • Specify the directories of its modules in the parent POM.

Example 5

Scenario

com.mycompany.app:my-app:1’s POM

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
</project>

com.mycompany.app:my-module:1’s POM

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-module</artifactId>
  <version>1</version>
</project>

Directory Structure

.
 |-- my-module
 |   `-- pom.xml
 `-- parent
     `-- pom.xml

Solution

Applying the three rules, the solution is as follows:

com.mycompany.app:my-app:1’s POM

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
  <packaging>pom</packaging> 
  <!-- Change the packaging value to pom -->
 
  <modules>
    <module>../my-module</module>
  </modules>
  <!-- Declare the path of the module being aggregated -->
</project>

com.mycompany.app:my-module:1’s POM

<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
    <relativePath>../parent/pom.xml</relativePath>
  </parent>
  <!-- Declare the parent project being inherited, and if the parent project is not installed, declare its relative path -->
  <artifactId>my-module</artifactId>
</project>

Note: Property inheritance follows the same strategy as inheritance used by the POM itself.

Project Interpolation and Variables

One of the practices Maven encourages is to avoid repetition. However, in certain cases, you may need to use the same value in multiple locations. To ensure that a value is specified only once, Maven allows you to use your own variables and predefined variables within the POM.

For example, to access the project.version variable, you can reference it like this:

<version>${project.version}</version>

One factor to note is that, as mentioned above, these variables are processed after inheritance. This means that if a variable is defined in the parent project and overridden in the child project, the child project will ultimately use its own definition rather than the parent project’s definition.

Available Variables

Project Model Variables

Any single value element field in the POM can be referenced as a variable. For example, ${project.groupId}, ${project.version}, ${project.build.sourceDirectory}, and so on. Refer to the POM reference documentation for a complete list of properties.

These variables are referenced with the prefix project.. You may also encounter references with the prefix pom. or without any prefix; these forms are deprecated and should no longer be used.

Special Variables

project.basedir The directory where the current project resides.
project.baseUri The directory where the current project resides, represented as a URI. Since Maven 2.1.0
maven.build.timestamp Represents the timestamp when the build started (in UTC). Since Maven 2.1.0-M1

You can customize the format of the build timestamp by declaring the maven.build.timestamp.format property:

<project>
  ...
  <properties>
    <maven.build.timestamp.format>yyyy-MM-dd'T'HH:mm:ss'Z'</maven.build.timestamp.format>
  </properties>
  ...
</project>

The format pattern must comply with the rules outlined in the SimpleDateFormat API documentation. If this property is not present, the format defaults to the value given in the example.