Maven Modules

Goals

Concepts

Library

Lesson

As your applications gets more and more complex, it becomes increasingly important to organize the project modules in some meaningful way. Logically many of the modules natural separate into layers or design patterns. On a higher level the modules need to be defined in a way so that they have access to the other modules they depend on, but remain loosely coupled so that they can be understood independently and even reused. Key to this organization is the project definition, and Maven provide several facilities for separating concerns at the project level.

Properties

Using properties to consolidate dependency version definitions.
<project …>
  …
  <properties>
    <project.build.sourceEncoding>UTF-8</…>
    …
    <guava.version>25.0-jre</guava.version>
    <junit.version>4.12</junit.version>
    <hamcrest.version>1.3</hamcrest.version>
  </properties>

  <dependencies>
    …
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>${guava.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hamcrest</groupId>
      <artifactId>hamcrest-library</artifactId>
      <version>${hamcrest.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  …

Before organizing modules across projects, it is important to understand how to organize the existing dependencies within a single Maven project. Maven properties are similar to constant variables. As in a computer program, these constants they allow you follow the Don't Repeat Yourself (DRY) principle. You can use Maven properties to move the definition of certain values to a common location in the POM so that they can be easily changed and then referenced later.

You already learned how to define properties when you first used Maven, setting the maven.compiler.source and maven.compiler.target properties. The Maven Compiler Plugin recognizes these two properties as indicating the version of Java being compiled. You can also define custom properties in this section, using the property name as the name of the XML child element. You can then reference these properties elsewhere within POM using the form ${property.name}. Properties within Maven usually use the full-stop dot character to separate multiple words.

A common use of Maven properties is to define the dependency versions at the top of the Maven file. When a newer version of the dependency is available, the developer merely needs to update the version definition in the properties section rather than searching for the dependency declaration itself. This technique becomes even more useful with child projects, as explained under Inheritance.

Project Properties

Maven conveniently provides predefined properties, many of them representing the information defined under the root <project> element. The names of these properties begin with a project. prefix. One of the most common examples of project properties is project.version, the value of which reflects the project version defined in <project><version>. Here are a few other common and useful project properties.

project.basedir
The base directory of the project. This is the directory containing pom.xml.
project.groupId
The project group ID.
project.artifactId
The project artifact ID.
project.version
The project version string.
project.name
The name of the project
project.build.directory
The directory where Maven places files generated by the build. This defaults to target.

Modularization

You've already been dividing your project into modules using Java packages: a package containing the business objects of application; packages representing layers of concern; and packages containing the repository pattern interface and its implementations; to name just a few examples. These modules have all been placed inside a single project.

Project Modules for a Car Rental Agency
vehicle-model
Domain model for vehicles. Contains the Vehicle interface and other business objects.
rental-repository
Defines a repository for storing and accessing vehicles for the rental agency.
rental-repository-database
A implementation of the repository that stores cars in a database
agency-web
A web application providing a browser-based user interface for accessing the rental repository.
agency-cli
A command-line interface client for updating the database.

As your program grows, it will become helpful to modularize at a higher level by separating the project itself into multiple related projects. You already did this briefly when creating data structures from scratch, organizing them in a separate datastruct project. Just as a single project references other dependencies available via the Maven Central Repository, once you break down your project into multiple projects they can reference each other using the same dependency mechanism Maven provides. The figure shows one possible modular arrangement of projects for a car rental agency named Rent-A-Car.

Monorepos

As a matter of course each project is placed under version control in a separate Git repository. This allows each project to evolve separately, allowing for a looser coupling among dependencies. If you were to have completed your datastruct project, for example, it would probably change infrequently; there would be no need to rebuild it each time your main program added a new feature. Moreover such a general library would likely be used by many programs, and there would be no point in placing it in any one program's repository. Each Maven project in the Rent-A-Car application might then be placed in a separate Git repository in the projects/ tree.

Monorepo for a Car Rental Agency

In some circumstances the Maven project modules are highly related. Although the modules are separated by concern, all the modules may be changing frequently. Moreover changing an interface in one module may require simultaneous updates of implementations in other modules.

In this scenario a configuration using multiple Git repositories would require multiple branches for each change. Pull requests across repositories would become cumbersome to manage and difficult to review. In such cases it is acceptable to place multiple Maven projects into a single source control repository called a monorepo. Each Maven project would typically be placed in a separate repository subdirectory, as illustrated in the figure.

Aggregation

Aggregate POM for Rent-A-Car.
<project …>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example.rentacar</groupId>
  <artifactId>rentacar</artifactId>
  <version>1.2.3</version>
  <packaging>pom</packaging>

  <modules>
    <module>vehicle-model</module>
    <module>rental-repository</module>
    <module>rental-repository-database</module>
    <module>agency-web</module>
    <module>agency-cli</module>
  </modules>
</project>

Whether or not your Maven modules are in the same source control repository, if several of them relate to the same application you'll probably want to build them all at the same time. Maven allows you to create an aggregate POM which “aggregates” or groups several Maven projects together. Performing a Maven command on the aggregate POM will invoke the command on all the aggregated projects as well.

A POM can specify an optional <packaging>, which defaults to jar if this is not included. An aggregate POM explicitly indicates a <packaging> of pom. An aggregate POM also contains a <modules> section; each child <module> identifies a the directory of a Maven project to be included.

Example aggregate POM location.

A typical location for an aggregate POM is in the directory above the modules, as shown in the figure. In this example, you could clean and package the entire Rent-A-Car project by invoking a Maven command in the directory of the aggregate POM. Maven would invoke the command on all of the defined modules in their appropriate directories.

cd rentacar
mvn clean package

Inheritance

The agency-cli module inheriting properties from a parent POM.
<project …>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example.rentacar</groupId>
  <artifactId>rentacar</artifactId>
  <version>1.2.3</version>
  <packaging>pom</packaging>
  …
  <properties>
    <project.build.sourceEncoding>UTF-8</…>
    …
  </properties>
  …
<project …>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.example.rentacar</groupId>
    <artifactId>rentacar</artifactId>
    <version>1.2.3</version>
  </parent>

  <groupId>com.example.rentacar</groupId>
  <artifactId>agency-cli</artifactId>
  <version>1.2.3</version>
  …
  <properties>
    …
    <project.build.sourceEncoding>UTF-8</…>
  </properties>
  …

Maven also provides a facility for creating a parent/child relationship between modules. The central benefit of this relationship is inheritance: as with Java subclasses, Maven modules inherit all the properties defined in a parent module. For example you could declare the default encoding <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> in the parent module, and never have to define it again in the child modules, unless you want to override it.

While aggregation is defined “downward” (that is, it is defined by the aggregate POM, not the included modules), inheritance is defined “upward”. Any module can define a parent/child relationship by indicating another module as its parent in the <parent> section of the POM. Such a relationship is defined, not by the location of the parent, but by its coordinates—just like a dependency. The parent module must have a <packaging> of pom.

Dependency Management

Beyond defining properties to be used in child POMs, Maven provides an even more elegant solution for defining dependency versions at the parent module level. The <dependencyManagement> section in a parent POM allows you to define versions and scopes of some dependencies which might be used, both in the same POM and in child POMs. This section contains a <dependency> subsection which is formed just like the <dependency> section you've been using. The difference is that inside <dependencyManagement><dependency> you are merely configuring the modules as possible dependencies.

Rent-A-Car parent POM with dependency management.
<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.example.rentacar</groupId>
  <artifactId>rentacar</artifactId>
  <version>1.2.3</version>
  <packaging>pom</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <jsr305.version>3.0.2</jsr305.version>
    <guava.version>25.0-jre</guava.version>
    <junit.version>4.12</junit.version>
    <hamcrest.version>1.3</hamcrest.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.google.code.findbugs</groupId>
        <artifactId>jsr305</artifactId>
        <version>${jsr305.version}</version>
        <optional>true</optional>
      </dependency>
      <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>${guava.version}</version>
      </dependency>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-library</artifactId>
        <version>${hamcrest.version}</version>
        <scope>test</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

In each child POM you will still need to declare that module's dependencies. You do not however have to indicate the version, scope, or optionality of the dependencies, because the dependencies are being managed in the parent POM. You can still override any details of the dependency if you wish—by indicating a different scope explicitly, for example.

The agency-cli module with dependencies managed by its parent POM.
<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.example.rentacar</groupId>
      <artifactId>rentacar</artifactId>
      <version>1.2.3</version>
  </parent>

  <artifactId>agency-cli</artifactId>

  <dependencies>
    <dependency>
      <groupId>com.google.code.findbugs</groupId>
      <artifactId>jsr305</artifactId>
      <version>${jsr305.version}</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>${guava.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hamcrest</groupId>
      <artifactId>hamcrest-library</artifactId>
      <version>${hamcrest.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Super POM

All Maven projects use inheritance. If you do not declare a parent POM, your project will inherit from the so-called Super POM, the Maven default project that sits at the root of the inheritance hierarchy. The Super POM is what configures the default directory layout and plugin settings. The Super POM is similar to java.lang.Object in the Java inheritance hierarchy.

When Maven builds your project, it takes the content of the project, its parent(s), and the Super POM to create a single project model. You can use the Maven Help Plugin effective-pom goal to generate an effective POM which shows this merged model as if it were stored in a single POM.

Showing the effective POM of a project.
mvn help:effective-pom

Test JARs

A prominent stumbling block with large projects containing unit tests is how to share those tests among dependencies. When you add a dependency to a project, you get access to that dependency's code—but not its tests. A dependency's unit tests are not included the JAR Maven produces.

Imagine you are wanting building a series of related projects for a farm. Your Maven module defining the BarnyardManager interface may use a FakeBarn for testing storage of equipment other items in a barn. Because FakeBarn is in the src/test/ subdirectory, Maven will not include in the resulting barnyard-1.2.3.jar file; FakeBarn is only used for testing. But what if the module for FarmService would like to use FakeBarn in its tests?

The Maven JAR Plugin provides a test-jar goal for producing a JAR containing only the tests of a project. By default this goal is bound to the package life cycle phase. During packaging this goal produces a separate artifact with a -tests suffix to the base name, such as barnyard-1.2.3-tests.jar, alongside the main artifact.

Barnyard module with test JAR.
<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.example.farm</groupId>
  <artifactId>barnyard</artifactId>
  <version>1.2.3</version>

  …
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
          <execution>
            <goals>
              <goal>test-jar</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

To include a test JAR as a dependency, you must indicate the coordinates of the library as normal but specify its <type> to be test-jar. This way Maven will know how to find the artifact with the -tests base filename suffix.

Farm service module using barnyard test JAR.
<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.example.farm</groupId>
  <artifactId>service</artifactId>
  <version>1.2.3</version>

  …
  <dependencies>
    …
    <dependency>
      <groupId>com.example.farm</groupId>
      <artifactId>barnyard</artifactId>
      <version>1.2.3</version>
      <type>test-jar</type>
      <scope>test</scope>
      </dependency>
  </dependencies>
</project>

Resources

For the vast majority of projects using the standard directory layout, Maven will look for resources in the src/main/resources/ and src/test/resources/ directories and copy them to the appropriate location in the target/ directory. This functionality is built into Maven, and is handled by the Maven Resources Plugin.

You can specify that other directories also containing resources, and they will be copied, too. Add them in the <build> section under <resources>, or <testResource> for those files only related to tests. Each resource is added as a separate <resource><directory> or <testResource><directory>, respectively. For example you might add a section <resources><resource><directory>${project.basedir}/screenshots</directory></resource</resources> to copy all images in the project's screenshots/ directory and include them in the generated artifact.

Filtering

By default Maven copies all resources with no changes. Maven also allows you to enable resource filtering, a useful feature that performs property replacements in the resource files when they are copied. To enable filtering, add <filtering>true</filtering> inside any <resource> or <resource> definition. Most commonly instead of adding a new resource directory, you would instead redefine the existing resource directory with filtering enabled, as shown in the figure.

Turning on filtering for for the src/main/resources/ directory.
<project …>
  …
  <build>
    …
    <resources>
      <resource>
        <directory>${project.basedir}/src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
    …

Once filtering is turned on, Maven will replace all properties variables in the resources with their values from the build, using the same ${property.name} format as is used in the POM. You could use filtering to include the name and current version of your application in its readme.txt file. The following example assumes that the project defines the project name using <name>Down on the Farm<name>.

src/main/resources/readme.txt before filtering.
Welcome to ${project.name}.
The current version is ${project.version}.
target/classes/readme.txt after filtering.
Welcome to Down on the Farm.
The current version is 1.2.3.

Profiles

One of the trademarks of a robust build system is that it is repeatable: it performs the same thing every time the build is initiated. It is essential that tests function the same on regardless of who runs the build, and that the artifact generated works the same as long as its coordinates remain unchanged. Nevertheless there is sometimes a need for variations in the build. Some developers may not want to generate extensive documentation during day-to-day development. Certain systems may need slightly different configurations based upon whether they are running Windows or Linux. Maven allows you to place build variations inside a profile, which is almost like a POM inside a POM.

Defining Profiles

Profiles are most commonly defined in the POM in a section called <profiles>. Each <profile> section should normally be given an <id> so that it can be manually activated later. A profile can defines properties, plugins, and settings that do not come into effect until the profile is activated. If the profile is activate, its contents effectively are integrated into the overall POM.

In day-to-day development you may have no need to generate a separate a JAR containing your source code—after all you, already have the source code to do the build. Likewise you may not wish to generate a JAR file containing Javadoc information, as this adds time to the build. But when it comes time to distribute your product, you may wish to distribute source JARs along with it, as well as a JAR containing API documentation as a reference.

You could create a profile with the ID release profile that would declare the Maven Source Plugin for producing a source JAR. The jar-no-fork goal bundles all the sources as part of the same build life cycle, and is bound by default to the package phase. This profile could also declare the Maven Javadoc Plugin using the jar goal, which is also bound by default to the package phase.

Including additional plugins in a separate profile.
<project …>
  …
  <profiles>
    <profile>
      <id>release</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.1</version> <!-- use the latest version available -->
            <executions>
              <execution>
                <goals>
                  <goal>jar-no-fork</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>3.0.0</version> <!-- use the latest version available -->
            <executions>
              <execution>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>
</project>

You can also use a profile to change the configuration of a plugin defined in the main <build> section or that comes defined by default in Maven. Consider a typical development cycle, in which developers continually build an application on their local machines. During development it is often useful to step through the code using a debugger, which requires the use of debugging information the Java compiler places in .class files by default. For the production build, however, you may want Maven to generate a smaller artifact with no debug information, and you may even want the compiler to provide additional optimizations. Both settings are available by configuring the Maven Compiler Plugin, which you can place in a normal <plugin> section placed inside a profile definition.

Turning off debug generation for a plugin in a profile.
<project …>
  …
  <profiles>
    <profile>
      <id>production</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version> <!-- use the latest version available -->
            <configuration>
              <debug>false</debug>
              <optimize>true</optimize>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>
</project>

You can just as easily define properties in a profile, which you can use elsewhere in the POM. The following example provides equivalent functionality.

Using a property defined in a plugin to turn off debug generation for a plugin in a profile.
<project …>
  …
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version> <!-- use the latest version available -->
        <configuration>
          <debug>${compile.debug}</debug>
          <optimize>${compile.optimize}</optimize>
        </configuration>
      </plugin>
    </plugins>
  </build>
  …
  <profiles>
    <profile>
      <id>production</id>
      <properties>
        <compile.debug>false</compile.debug>
        <compile.optimize>true</compile.optimize>
      </properties>
    </profile>
  </profiles>
</project>

Activating Profiles

The contents of a profile will not have any influence on the build unless it is an active profile. By default a profile is not active. There are several ways to make a profile active, and multiple profiles can be active at the same time.

Manual Profile Activation

You can manually activate one or more profiles explicitly on the command line when you invoke Maven. List the profiles, separated by commas, as an argument to the --active-profiles (-P) command-line parameter. For example you could activate both the release and production profiles shown above using -P release,production.

Profile Activation by Default

You can also specify conditions under which a profile will be activated. These conditions appear in the <activation> section of the profile, and the simplest condition is <activeByDefault>. If you make a profile active by default, there is no need to manually specify a profile at all. In the following example, the development profile will be enabled, turning on compiler debugging and disabling optimization, even if no profiles were indicated on the command line.

Making profiles active by default.
<project …>
  …
  <profiles>
    <profile>
      <id>development</id>
      <activation>
        <activByDefault>true</activeByDefault>
      </activation>
      <properties>
        <maven.compiler.debug>false</maven.compiler.debug>
        <maven.compiler.optimize>true</maven.compiler.optimize>
      </properties>
    </profile>
  </profiles>
</project>
Profile Activation by Java Version

You can specify that a profile becomes active based upon a version of the JDK using the <jdk> element. The profile will become activated when the JDK version starts with the given string. For example, <jdk>1.8</jdk> would match Java builds such as 1.8.0_162. You can indicate a range of versions, separating the lower and upper bounds using a comma; a square bracket indicates an inclusive bound, while a parenthesis indicates an exclusive bound. You can leave off one bound and use a parenthesis to indicate an unbounded range. The range <jdk>[1.8,)</jdk> for example indicates Java 8 and above. The Disabling Javadoc Errors on Java 8 and Above section below provides a real-life example.

Profile Activation by Property

You can even activate a profile based upon which properties are set. Provide a <property> section with both a <name> and <value> inside <activation>. The following example turns on compiler debugging if the debug property is set (such as by using -Ddebug=true on the command line).

Turning on compiler debugging if a debug flag is set.
<project …>
  …
  <profiles>
    <profile>
      <activation>
        <property>
          <name>debug</name>
          <value>true</value>
        </property>
      </activation>
      <properties>
        <maven.compiler.debug>true</maven.compiler.debug>
      </properties>
    </profile>
  </profiles>
</project>
Other Profile Activation

There are other ways to activate profiles, including checking specific system versions and the existence of files, as shown in the following example. See Maven: The Complete Reference: 5.3. Profile Activation for more information, and Introduction to Build Profiles: How can a profile be triggered? for more examples.

Other profile activation techniques.
<project …>
  …
  <profiles>
    <profile>
      <id>demo</id>
      <activation>
        <os>
          <name>Windows XP</name>
          <family>Windows</family>
          <arch>x86</arch>
          <version>5.1.2600</version>
        </os>
        <file>
          <exists>good.properties</exists>
          <missing>bad.properties</missing>
        </file>
      </activation>
      <properties>
        <maven.compiler.debug>true</maven.compiler.debug>
      </properties>
    </profile>
  </profiles>
</project>

Settings

Not all configuration information lives in the POM. It is crucial that passwords and other credentials are defined outside the POM, in a location that is not under source control. Although you may provide custom configuration loading from within your application, that configuration information may not be easily accessible to configure plugins in the POM itself.

Maven allows for settings to be defined outside the POM in a settings.xml file. Settings are defined on two levels: for the user, and for the Maven installation. Each has a settings.xml file, which you may create if it isn't already present. Global settings are located relative to the Maven installation, while each user's settings are located relative to the user's home directory.

Global Settings
${maven.home}/conf/settings.xml
User Settings
${user.home}/.m2/settings.xml

Servers

A primary use of settings.xml is to declare credentials for server access. For example, if you wish to deploy your project to the Maven Central Repository, you will likely use the Nexus Staging Maven Plugin.

Example pom.xml plugin configuration for deploying to Maven Central Repository.
<project …>
  …
  <build>
    <plugins>
      <plugin>
        <groupId>org.sonatype.plugins</groupId>
        <artifactId>nexus-staging-maven-plugin</artifactId>
        <version>1.6.7</version>
        …
        <configuration>
          <serverId>ossrh</serverId>
          <nexusUrl>https://oss.sonatype.org/</nexusUrl>
           …
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Permission to deploy to the Maven Central Repository is usually defined on a per-user basis, each of which has different Nexus credentials provided by Sonatype. Rather than including credentials to the Sonatype server in the POM istelf, each user who has deploy permissions will include their credentials in the user settings. The plugin configuration in the POM identifies using <serverId> which server configuration in the settings contains the credentials.

Example settings.xml proding server credentials.
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  …
  <servers>
    <server>
      <id>ossrh</id>
      <username>jdoe</username>
      <password>secret</password>
    </server>
  </servers>
  …
</settings>

Profiles

The settings.xml file can be used to define new profiles independent of the the POMs themselves. A profile in the settings can define properties that are used for one or more POMs. One potential use case is a simpler mechanism than the <server> section for providing credentials to be used in a POM, as shown in the following example. A POM could use the ${my.server.username} and ${my.server.password} properties in place of the actual values, requiring users to define these properties in their settings.

Defining properties in a settings profile.
<settings …>
  …
  <profiles>
    <profile>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <my.server.username>jdoe</my.server.username>
        <my.server.password>secret</my.server.password>
      <properties>
    </profile>
  </profiles>
  …
</settings>

Profile Activation

Besides defining profiles, a settings.xml file can specify which profiles are activated. The profiles identified in the <activeProfiles> section will enable profiles in the settings or even those that appear in individual projects. A production server (or at least the server user with permissions to deploy the application) might indicate the release and production profiles in the settings.xml file so that they need not be manually indicated on the command line.

Activating profiles in settings.xml.
<settings …>
  …
  <activeProfiles>
    <activeProfile>release</activeProfile>
    <activeProfile>production</activeProfile>
  </activeProfiles>
</settings>

Review

Summary

TODO summary of property sources

TODO sections that can be in a profile

Gotchas

In the Real World

Filtering Properties Files Using ISO-8859-1

In earlier versions of Java, properties files were assumed to use the ISO-8859-1 charset, and resource bundles would misinterpret any non-ASCII characters if a properties file used a different charset. Java 9 now supports UTF-8 properties files, and libraries such as Rincl support UTF-8 in properties files independent of the Java version. If you need to support plain resource bundles in earlier versions of Java, you must specify a different encoding by configuring the Maven Resources Plugin.

The Maven Resources Plugin provides no direct way to specify distinct encodings for different file types; you must add a separate execution for each. Further complicating things is that by default the Maven Resources Plugin resources goal is bound to the process-resources phase using the execution ID default-resources, and the testResources goal is bound to the process-test-resources phase using the execution ID default-testResources. Each performs its own default copy procedure, so if you add a separate execution you will still need to modify the configuration of the default execution. See Including and excluding files and directories for more information on how to specify which resources are included.

<project …>
  …
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>3.1.0</version> <!-- use the latest version available -->
         …
        <executions>
          <!-- ignore properties files in the default execution -->
          <execution>
            <id>default-resources</id>
            <configuration>
              <resources>
                <resource>
                  <directory>${project.basedir}/src/main/resources</directory>
                  <filtering>true</filtering> <!-- whether non-properties files are filtered -->
                  <excludes>
                    <exclude>**/*.properties</exclude>
                  </excludes>
                </resource>
              </resources>
            </configuration>
          </execution>
          <execution>
            <id>filter-properties-files</id> <!-- any custom ID may be used -->
            <goals>
              <goal>resources</goal>
            </goals>
            <configuration>
              <encoding>ISO-8859-1</encoding>
              <resources>
                <resource>
                  <directory>${project.basedir}/src/main/resources</directory>
                  <filtering>true</filtering>
                  <includes>
                    <include>**/*.properties</include>
                  </includes>
                </resource>
              </resources>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Disabling Javadoc Errors on Java 8 and Above

Starting with Java 8, which added a Javadoc DocLint feature for checking Javadoc format, the Maven Javadoc Plugin began considering as errors Javadoc problems that were previously considered warnings. This means that many projects would no longer build when upgrading to Java 8 if they contained Javadoc problems. The Maven Javadoc Plugin starting with version 3.0.0 provides an <additionalOptions> configuration that allows you to specify a -Xdoclint option for using the DocLint level to turn off DocLint, such as <additionalOptions>-Xdoclint:none<additionalOptions>. This configuration was named <additionalparam> before version 3.0.0; see MJAVADOC-475. However older versions of Javadoc would fail because they do not recognize the -Xdoclint option option.

One solution was to specify a profile activated by Java version that would set the additional parameters only for versions of Java that supported it.

<project …>
  …
  <profiles>
    <profile>
      <id>release</id>
      <build>
        <plugins>
          …
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadocplugin</artifactId>
            <version>3.0.0</version> <!-- additionalOptions named additionalparam in previous versions -->
            <executions>
              <execution>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <additionalOptions>${doclint.params}</additionalOptions>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
    …
    <profile>
      <id>disable-doclint-java8</id>
      <activation>
        <jdk>[1.8,)</jdk>
      </activation>
      <properties>
        <doclint.params>-Xdoclint:none</doclint.params>
      </properties>
    </profile>
  </profiles>
</project>

See Maven is not working in Java 8 when Javadoc tags are incomplete and Turning off doclint in JDK 8 Javadoc for more discussion and history.

Think About It

A monorepo is not always appropriate even if projects are related. Here are some of the factors to take into consideration that would indicate a monorepo might be appropriate.

Self Evaluation

Task

You have been keeping your Booker application modularized with the evolution of the program. The interfaces and classes that model books and periodicals should already be in a separate model subpackage, although you might have named the subpackage pub or something else. You probably have I/O utility classes in a util subpackage or similar. Your publication repository interface is likely in a repo or repository subpackage, and each repository implementation in some still lower-level package.

Different applications other than the Booker command-line interface program may wish to use the publication model. Applications may want to pick and choose which repository implementation to use as a dependency, without including all of them. Yet the application, model, and repository classes are highly related; it would ease development to keep them in the same repository.

Convert your booker repository into a monorepo.

Add a version switch to the Booker CLI that will print out the program name and current version. Use the version of the Maven CLI subproject, but do not hard-code this value into your program. Use Maven filtering with some sort of resource or configuration file so that Maven updates the version automatically with each build. You can execute git --version or mvn --version to see an example of the kind of output expected of your program.

Option Alias Description
list Lists all available publications.
load-snapshot Loads the snapshot list of publications into the current repository.
purchase Removes a single copy of the book identified by ISBN from stock.
subscribe Subscribes to a year's worth of issues of the periodical identified by ISSN.
--debug -d Includes debug information in the logs.
--help -h Prints out a help summary of available switches.
--isbn Identifies a book, for example for the purchase command.
--issn Identifies a periodical, for example for the subscribe command.
--locale -l Indicates the locale to use in the program, overriding the system default. The value is in language tag format.
--name -n Indicates a filter by name for the list command.
--type -t Indicates the type of publication to list, either book or periodical. If not present, all publications will be listed.
--version -v Displays the name and current version of the Booker command-line program.

See Also

References

Resources