Maven conventions¶
General¶
- Do not set the project
<name>
. It is redundant and results in additional maintenance when changing artifact IDs. - If you really, definitively need to add information beyond what is contained in
<groupId>
and<artifactId>
, use<description>
. - In multi-module projects, always specify the
<relativePath>
of the<parent>
.
Project structure¶
Or projects usually follow a common structure:
- Maven modules belong to either of the following:
- an application/service the project provides ("bar")
- a business domain module ("foo") which is used by one or more applications
- Applications and modules are further subdivided into business, core and other aspects.
For example, a project that deals with an ESB and provides AWS lambdas and REST APIs might use the following directory structure:
The directories in parallelogram boxes (▱) are aggregator modules, the other ones result in either a jar or war.
IDs of artifacts and groups¶
The group ID of the POM in the project root directory represents the entire project (e.g. com.example.fortytwo
). Its artifact ID is identical to the last segment of the group ID.
All other group IDs and artifact IDs are derived from the root group ID and the relative paths as follows:
- For each nested directory, the POM's group ID consists of the group ID of the parent plus the directory name. The artifact ID is identical to the last segment of the group ID.
- This process continues until a sensible cut-off point is reached. Usually, this would be the aggregator pom of a business domain module, service or application.
- Below the cut-off point, the group ID remains unchanged. For each nested directory, the POM's artifact ID consists of the parent's artifact ID plus the directory name.
For the directory structure shown above, these rules result in the following artifact coordinates:
Group ID | Artifact ID |
---|---|
com.example.fortytwo |
fortytwo |
com.example.fortytwo.modules |
modules |
com.example.fortytwo.modules.foo |
foo |
com.example.fortytwo.modules.foo |
foo-business |
com.example.fortytwo.modules.foo |
foo-core |
com.example.fortytwo.modules.foo |
foo-store |
com.example.fortytwo.applications |
applications |
com.example.fortytwo.applications.bar |
bar |
com.example.fortytwo.applications.bar |
bar-business |
com.example.fortytwo.applications.bar |
bar-core |
com.example.fortytwo.applications.bar |
bar-interfaces |
com.example.fortytwo.applications.bar |
bar-interfaces-esb |
com.example.fortytwo.applications.bar |
bar-interfaces-esb-incoming |
com.example.fortytwo.applications.bar |
bar-interfaces-esb-outgoing |
com.example.fortytwo.applications.bar |
bar-interfaces-lambda |
com.example.fortytwo.applications.bar |
bar-interfaces-rest |
com.example.fortytwo.applications.bar |
bar-lambdacontainer |
com.example.fortytwo.applications.bar |
bar-webapp |
Versions¶
- Version numbers follow the Semantic Versioning specification, which defines three version segments:
MAJOR.MINOR.PATCH
.- All versions always include
MAJOR.MINOR
, for example0.9
,1.0
or2.48
. - When
PATCH
is0
, leave it out: For example, we write2.7
(instead of2.7.0
) and a later hotfix release would be called2.7.1
.
- All versions always include
- The version number of the project is the same for all its modules and is specified in the top-level module.
- Submodules specify their parent using group ID, artifact ID and version, without any
${...}
expressions. - Submodules inherit the version and group ID of their parent; the latter may be overridden.
- For example, the "applications" project above sets its group ID to
com.example.fortytwo.applications
- if it didn't, it would inherit the valuecom.example.fortytwo
from its parent.
- For example, the "applications" project above sets its group ID to
- Release version numbers (non-SNAPSHOTs) are never set manually. This is only done during the release process.
- To define the upcoming release version, set the related snapshot version manually.
- For example, when fortytwo
2.7
was released, the project version was changed automatically to2.8-SNAPSHOT
. Now the developers decide that the upcoming version should not be called2.8
, but3.0
. A developer therefore changes the project version manually from2.8-SNAPSHOT
to3.0-SNAPSHOT
. - The version number needs to be changed in all modules. To make this process easier, use
mvn versions:set -DgenerateBackupPoms=false
and enter the new version.
- For example, when fortytwo
Managing dependencies¶
- Keep things tidy. Do not declare anything that is not used anymore, or even worse, not yet used.
- Order entries alphabetically by group ID and artifact ID. In the rare case where order matters for compilation, add a comment explaining the situation. See Sorting POMs below.
Dependencies¶
- Always declare dependencies explicitly, do not rely on transitive dependencies.
mvn dependency:analyze
helps with that. - Never declare version numbers or scopes in dependencies, but only in the project's
<dependencyManagement>
block (see below). - Declare the
<optional>
flag only on a dependency, never in<dependencyManagement>
. -
Do not declare dependencies in POM modules, only in JAR/WAR modules. Even if multiple child modules have the same dependency, do not move up the dependency
Rationale
This rule avoids declared-unused-dependency warnings from
mvn dependency:analyze
.
Dependency Management¶
-
Use exactly one
<dependencyManagement>
block inside the top-level module of the project (the one that defines the project's version number and inherits from a corporate-level POM), not the child modules. In the example above, this would be thecom.example.fortytwo:fortytwo
POM.Rationale
This way, when preparing a release, only one module needs to be checked for SNAPSHOT dependencies.
-
In
<dependencyManagement>
entries for projects which are part of the aggregator (e.g. dependencies between fortytwo projects, see above), specify the version using the variable${project.version}
.Warning
Do not use
${project.groupId}
as the group ID may be different in the POM that has the real dependency. -
Do not add
<dependencyManagement>
entries for artifacts which inherit from the current one. For example, if the fortytwo project inherits from the corporate-level POM, that corporate POM should not have a<dependencyManagement>
entry specifying a certain fortytwo version.Rationale
Doing so would cause a chicken-and-egg problem when releasing each of the POMs the first time, because a release can only depend on other releases, not SNAPSHOTs, but there are no releases yet. Although introducing the entry later would work, it would confuse the hell out of everybody, so the rule is to avoid this altogether.
Configuring plugins¶
- Specify the plugin
<version>
inside<build><pluginManagement>
, never in<build><plugins>
. -
Plugin
<executions>
,<configuration>
and<dependencies>
elements are also specified inside<build><pluginManagement>
.Rationale
Apart from consistency, this makes overriding configuration in inheriting projects easier.
-
The rules above should result in
<build><plugins><plugin>
entries that only contain<groupId>
and<artifactId>
.
Sorting POMs¶
Recent versions of the corporate POM configure the maven-sortpom-plugin. Just use mvn sortpom:sort
on your project to have everything in order.
Warning
Some test dependencies need to be in a certain order to override other dependencies. In this case, add an Ignore Section to ensure everybody can always invoke maven-sortpom-plugin
safely. Inside the section, always add a comment explaining the ordering details. See also rule OMG-10.
Bill of Materials (BOM)¶
- BOMs only consist of a
<dependencyManagement>
section, they do not declare dependencies. -
Multi-module projects should offer a BOM listing their jar children.
Rationale
This can be imported into the
<dependencyManagement>
of other projects to ensure all modules used in that project are at the same version. -
BOMs can also be used to group external dependencies for one topic, e.g.
testing-bom
ortools-bom
.Rationale
This avoids littering our POM landscape with endless combinations of the version numbers of the individual artifacts.
-
BOMs should only be used outside the project that they represent, not from within.
Rationale
Doing the latter can result in chicken-and-egg problems and is thus deemed too complicated.