Understanding Apache Maven – Part 9 – Versions in Maven

java, maven

Published: 2020-07 (July 2020)
Verified with: Apache Maven 3.6.3

Link to an index, to find other blogs in this series.

In Part 9 of the series, versions in maven are covered.

In Part 3, version schemes in Maven were briefly covered. A quick recap, a version is a string that uniquely identifies a set of changes made to the project. Maven does not understand version schemes and cannot compare versions using numeric (or alphanumeric) comparisons. Some plugins may build additional intelligence for such.

Maven uses the version as a coordinate in identifying an artifact.

Common Version naming conventions

Development cycle

During a development cycle, code is often altered. Changing version numbers for each change in code will produce too many throw-away versions. Each deployment where this artifact gets tested will need to update a version number. In addition, if other projects depend on such an artifact, it can cause ripple effects while updating the version number from each build. As a solution maven provides what is known as a SNAPSHOT version. A SNAPSHOT version is usually the same as the targeted final version with a suffix of -SNAPSHOT.

The project POM declares its coordinates with the SNAPSHOT suffix and iteratively builds newer artifacts. Metadata for the produced artifact gets an update, but the version remains constant throughout the development cycle. Deployments can rely on this SNAPSHOT version as a coordinate to get the latest artifact. Other projects that depend on this project can also avail the latest changes with a re-build and no other version changes.

A version with a SNAPSHOT suffix is a mutable artifact that eases the development cycle. Mutability is both a benefit and a liability. More on this later.

Release cycle

Once all desired group of changes are verified/tested, a project reaches a release cycle. The artifact produced from building the project can be marked immutable. Any further change would have to be scoped into another future build, with a different version. Such builds are commonly called release builds. The artifacts produced from this build are immutable. Version schemes for such artifacts may have no suffix or may have an alternate suffix such as -FINAL or -RELEASE or -GA etc.

Transitioning from SNAPSHOT to a release

There are several ways to convert a SNAPSHOT version to a release when the time comes. Versions can be manually altered prior to a build. The build sanctity is potentially violated with any change made in code, hence using an automated process to update the version are preferred. Two sample mechanisms commonly used are detailed below.

Using a versions-maven-plugin

(Group and artifact: org.codehaus.mojo:versions-maven-plugin). The plugin has a goal use-releases that removes the SNAPSHOT. The plugin has several other goals that are quite useful.

Link to the goals page for the versions-maven-plugin: https://www.mojohaus.org/versions-maven-plugin/plugin-info.html

Using a maven-release-plugin

(Group and artifact: org.apache.maven.plugins:maven-release-plugin) is another means of controlling versions. A goal update-versions in the plugin allows for setting versions.

Link to the goals page for the maven-release-plugin: https://maven.apache.org/maven-release/maven-release-plugin/plugin-info.html

What type of version to use in a POM?

Ideally, since a POM is modified during development, it is best to use the -SNAPSHOT suffix in the POM. A project during its release can shed the -SNAPSHOT through many means, two of which were covered above. In addition, the above mentioned plugins also support bumping the version number to the next SNAPSHOT for a future development cycle right after the build for the release completes.

Typically projects start with a 0.0.1-SNAPSHOT or a 1.0.0-SNAPSHOT. Following Semver 2.0 rules is heavily recommended for the numeric portion of the POM, since it provides visual cues for a developer’s understanding.

Common Version Strategy in Maven. Development cycles re-use the SNAPSHOT version, Release produces immutable artifact version.
Common Version Strategy in Maven. Development cycles re-use the SNAPSHOT version, Release produces immutable artifact version.

Controlling Versions in Maven

Version Ranges in Maven

Maven supports version ranges. At times it is possible for a POM to be a bit flexible in accepting a range of versions of a dependency. This flexibility can stem from some underlying assumptions such as backward compatibility or a minimal dependence on the said dependency. It is also a means of restricting an allowed version to be within a specified set of versions.

Hard versus Soft Requirements

Maven version values can either be a soft requirement or a hard requirement.

As the names suggest, a soft requirement is a replaceable version with the current value being the preferred version to use. If the dependency graph contains a different version with alternate requirements, it can be picked over the current version value. This is more often a single value than a range. Most POMs use a soft requirement. Specifying a version without any range or restrictions (for example 1.3.8 or 2.0.0-alpha) implies it is a soft requirement.

A hard requirement is a pattern that restricts the version to be selected. A hard requirement uses square brackets (inclusion) and parenthesis (exclusion) to determine allowed version values. A few examples of hard requirements are tabulated below.

RangeNotes
[1.0]Use exactly version 1.0.
(,1.0]Use any version <= 1.0 Flexible on versions before and including 1.0, Restrict any values above 1.0.
Note the initial comma.
[1.0,1.3]Use any version inclusive of 1.0 up until 1.3 including 1.3
[1.0,1.2)Use any version inclusive of 1.0 and above, until and excluding 1.2
[1.2,)Use any version inclusive of 1.2 and above.
(,1.0],[1.2,)Use any version <=1.0 or any version >= 1.2
(,1.1),(1.1,)Use any version except 1.1
Version Range patterns

Version ranges are a powerful option. One of the major drawback of using ranges, in general, is a lack of build reproducibility. There is no guarantee of the version that will be chosen if a range is provided. There are better options using dependencyManagement to control versions. This blog will also cover the maven-enforcer-plugin, which can do more with version ranges.

Using dependencyManagement and pluginManagement

As was covered in Part 5, versions for a dependency can be controlled using a dependencyManagement section.

A quick recap, the dependencyManagement element allows for a lookup reference where dependencies can be listed via their location coordinates that must include the GAV coordinates (groupId, artifactId, version), may include distinguishers (classifier, type) and may include exclusions, scope or an optional flag.

Dependencies then declared in the POM directly can be listed with just the groupId and artifactId, the rest of the information can be fetched from the lookup to the dependencyManagement block.

Not everything listed in the dependencyManagement block need be used in the actual POM, since none of the dependencies listed in the block are directly used in generating an effective POM.

As was covered in Part 8, versions for a plugin can be controlled using a pluginManagement section.

A similar recap for pluginManagement applies, where a plugins can be listed via their GAV coordinates, and may optionally include dependencies, configuration, and flags for extensions and inherited.

Similar to the pattern followed by dependencyManagement, plugins configured in pluginManagement are not directly used in the build, but are looked up, when encountered in the POM’s build section with a groupId and artifactId.

Using a maven-enforcer-plugin

Maven provides a very versatile tooling plugin to allow centralized control over the build environment from a single POM (inherited to child POMs) and allows for greater flexibility in version specifications by supporting version ranges.

The actual name of the plugin: Maven Enforcer Plugin – The Loving Iron Fist of Maven TM. Link to the goals of the plugin: http://maven.apache.org/enforcer/maven-enforcer-plugin/plugin-info.html

The enforcer plugin deserves a blog unto itself, but as a preview, the enforcer provides built-in rules and a capability to extend the same with custom rules via a rich Maven Enforcer Rule API. Custom rules require creating a custom enforcer, by extending the EnforcerRule. The custom rule is packaged as a jar and included as a dependency to the plugin. The rule can be invoked by including the fully-qualified class name under the configuration for the plugin.

Link to the enforcer API: http://maven.apache.org/enforcer/enforcer-api/writing-a-custom-rule.html

Enforcer provides built-in rules for bannedDependencies, dependencyConvergence, requirePluginVersions, requireReleaseVersions, requireSameVersions etc. and allows for custom rules to be defined, which can help control the versions and fail the build if the rule is not satisfied.

Link to built-in rules: http://maven.apache.org/enforcer/enforcer-rules/index.html

Handling Backward Compatibility

Projects, especially libraries, strive not to break backward compatibility. Backward compatibility is a guarantee that upgrading to the newer version of the library WILL NOT break existing usage. While this goal is utopian, there is a necessity to, at times, break backward compatibility, due to either security constraints or to allow enhancements that are not possible without such a breaking change.

In such situations, it is best to rename the groupId or artifactId so the dependency resolution conflicts are more visible and appropriate exclusions or changes in the project code can be made. Changing the groupId or artifactId provides the necessary separation to indicate a breaking change. It also allows for a continued support of the prior functionality, if deemed necessary.

That’s a wrap on this blog. Have fun !

Part 8
Maven Plugins
IndexPart 10
To Be Determined

Understanding Apache Maven – Part 8 – Maven Plugins

java, maven

Published: 2020-07 (July 2020)
Verified with: Apache Maven 3.6.3

Link to an index, to find other blogs in this series.

In Part 8 of the series, a deeper dive in Maven Plugins is covered.

In Part 4, Maven Lifecycles and Phases were introduced. A brief overview of Plugins and Goals were also included. A quick recap:

  • Apache Maven provides standard lifecycles.
  • Lifecycles have phases that execute sequentially.
  • Execution of maven is done via plugins which define goals.
  • Goals can be associated with phases (or may simply be run independent of phases).

What are plugins?

Maven is a plugin-execution framework. Plugins are an assembly of goals, code written as MOJOs (Maven’s plain Old Java Objects, Modern MOJOs are not restricted to being written in Java). Goals have names and can be bound to phases. A MOJO declares its goal name and optionally, a phase association, which binds the class to a part of a lifecycle.

Image displays a plugin-lifecycle relationship. Plugins define goals. Goals can be bound to phases. Goals from multiple plugins can be bound to a single phase.
Plugins define goals. Goals can be bound to phases. Goals from multiple plugins can be bound to a single phase.

A plugin is typically a .jar file which contains the MOJO classes and a META-INF/maven/plugins.xml. This plugins.xml is generated as a part of the maven execution of the plugin code.

Types of plugins

Broadly, plugins are of two types:

build plugins – configured in a project POM under the <build> element. Such plugins are executed as a part of the default (build) lifecycle.

reporting plugins – configured in a project POM under the <reporting> element. Such plugins are executed as a part of site lifecycle.

Furthermore, plugins can be classified as:

core plugins – Plugins where goals are bound to core phases (clean, compile, install, resources etc.)

packaging plugins – Plugins related to output artifact packaging. Examples include plugins for ear, jar, war etc.

reporting plugins – Plugins related to the site lifecycle and used to generate reports. Examples include checkstyle, javadoc, project-info-reports etc.

tooling plugins – Plugins related to general tooling during the maven execution. Examples include assembly, help, enforcer toolchains etc.

Plugins from Maven – versus – custom plugins

Official Maven plugins developed as a part of Apache Maven have a standard naming convention: maven-<plugin shortName>-plugin. This naming convention is reserved and SHOULD NOT be used by plugins which do not have a groupId of org.apache.maven.plugins and are not found on a maven repository under ord/apache/maven/plugins directory.

Plugins developed with other groupIds typically have a name of <plugin shortName>-maven-plugin.

How to learn about a plugin

Standard plugins from Apache Maven have a consistent site structure under a common site: https://maven.apache.org/plugins/index.html.

Each plugin landing page has a few menu items under its navigation panel. An Introduction page, which provides an overview of the plugin. A Goals page which lists all goals defined by the plugin and a deeper explanation of goal’s intent. A Usage page that provides configuration options and any relevant information regarding constraints for the plugin. An FAQ page for responses to frequently asked questions. There are additional pages for License and Download of the plugin as well.

In addition to the standard menu items, a plugin landing page can provide links to Examples and Project Documentation.

The plugin site is the best way to start understanding a plugin provided by Apache Maven. Plugins developed external to Apache Maven should attempt to follow similar conventions to ensure easier comprehension by the users.

How to use plugins in a project POM

Plugins are configured in a POM under either the <build> or the <reporting> or the <profiles> -> <profile> element. A plugin can be located using the standard maven G-A-V (GroupId-ArtifactId-Version) coordinates. In addition to the location coordinates, a plugin has a few other elements that are optional.

Extensions

The extensions is a flag to determine if Maven extensions from the plugin should be loaded. The value is a true or false, however, the current datatype in the schema is a String (for some technical reasons). The default value is false and it is rarely enabled. Typical use cases for enabling this is when defining a custom lifecycle or packaging types.

Inherited

Will be covered in a section below, but inherited is a boolean flag that is true by default. As with extensions the datatype in the schema is a String. Setting inherited to false prevents propagation of the configuration to any child POM of the current one.

Executions

The executions is a complex element and contains a set of execution elements. At its core, maven executes such definitions. An execution specified the set of goals to execute during the lifecycle. An execution is a complex element that has a unique id, a phase to bind one or more goals to, an inherited flag (similar to the one defined for a plugin, also set as a String datatype), a goals element which is a set of String goal elements that are bound to the phase, and a generic configuration element.

Dependencies

The dependencies is a complex element and contains a set of dependency elements. The dependency definitions listed here are used by the plugin and loaded by the plugin classloader.

Configuration

The configuration is a complex element which allows for a free-form DOM configuration used by the plugin. The configuration specifics are typically listed (and recommended, in case of custom plugins) in the Usage page for a given plugin.

A visual of the plugin element

Plugin Inheritance

Plugins have a inheritance logic similar to dependencies. A plugin declared in a parent POM is inherited into the child POM unless the parent declares the inherited flag to false. Setting the inherited flag to false breaks the inheritance.

In addition, a pluginManagement section in a POM functions the same way a dependencyManagement works for a dependency.

There is currently no equivalent in a bill-of-materials for a plugin, although, there is an open issue for introducing such a facility. Link: https://issues.apache.org/jira/browse/MNG-5588

Mixin Maven Plugin

Not officially an Apache Maven plugin, but the mixin-maven-plugin deserves a special mention. The plugin allows for including multiple pluginManagement sections without needing to inherit the plugins from a single parent. Using this plugin allows for a build behavior to be made more modular.

More about the mixin-maven-plugin: https://github.com/odavid/maven-plugins/tree/master/mixin-maven-plugin

Links to learn more

Introduction to Plugins: https://maven.apache.org/guides/introduction/introduction-to-plugins.html

Maven Plugins: https://maven.apache.org/plugins/index.html

Guide to configuring plugins: https://maven.apache.org/guides/mini/guide-configuring-plugins.html

Guide to developing plugins: https://maven.apache.org/guides/plugin/guide-java-plugin-development.html

Maven MOJO API: https://maven.apache.org/developers/mojo-api-specification.html

Plugin descriptor (Apache Maven 3.6.3): https://maven.apache.org/ref/3.6.3/maven-plugin-api/plugin.html

That’s a wrap on this blog. Have fun !

Part 7
Configuring Apache Maven
IndexPart 9
Versions in Maven