Apache Maven 1.x has reached its end of life, and is no longer supported. For more information, see the announcement. Users are encouraged to migrate to the current version of Apache Maven.

Best Practices

One of the biggest advantages of Maven is that it makes things easiest when best practices are followed. There are several recommended steps to take in setting up your build and developing the project that will help detailed here.

Follow Conventions

Many have complained that Maven forces a certain structure on a project. While this is not completely true (most settings are still configurable), it certainly does make it easier to use defaults.

From the layout of your source code to the layout of your project documentation, and all the standard goals in between, following conventions helps new users find their way around your project with minimal training and additional documentation.

For more information, refer to the Conventions Reference.

Reproduciblity

It is important to make sure a build is reproducible. This means a few things:

  1. That any user can check it out and build as is (or with minimal configuration)
  2. That you can check out a historical codebase and it should build as it did then
  3. That a generated artifact for a particular version of a project should always be built identically

Making sure any user can check it out and build means that you must avoid the need for properties specific to your environment. The best practices to follow here are:

  • Minimise the number of properties in $HOME/build.properties and ensure they are only those required for your particular environment, and that it is well known that they need to be set (eg HTTP proxy settings, J2EE server installation path)
  • Don't use absolute paths in properties and scripts for a project
  • If you must add an environment specific property, add it to build.properties in the project, but don't commit it to source control. Commit a sample properties file that other developers will know to copy and edit accordingly. However, wherever possible, use sensible defaults to avoid the need for this.

Being able to check out a historical codebase is more important in some environments than others, but often you don't know you need it until it is too late, so it is a good practice to follow.

  • Follow the property rules above as they will also cause old builds to fail as things change in a local environment
  • Make sure that releases use known versions of dependencies - don't depend on SNAPSHOTs that are subject to change and likely to be different the next time you build from when the original release way
  • Document the version of Maven and plugins installed when building the project. This information can be obtained with maven --info. Future versions of Maven aim to make this easier to keep consistent through the life of the project.

Finally, make sure generated artifacts are always built identically. This is important as two files with the same name, but different settings built in can become very confusing. Also, you often do not want to have to rebuild an artifact as it goes from staging to QA to production as it introduces the risk that it may be built differently (especially if the previous principles were not adhered to!) Some recommendations for this are:

  • Avoid the need to filter resources. While this can be useful in a development environment, it usually requires rebuilding of an artifact between different phases of deployment. The best alternative is to externalise the configuration - for example in J2EE (where this is a common occurrence), make sure all configurable information such as database connection properties are in the deployment descriptor, provided through JNDI outside of the webapp or other deployable item. This means the particular artifact can be deployed identically into different servers, with just the external configuration differing.
  • Only interpolate values in the POM with other POM values, not properties that might be user or environment specific.
  • Avoid the use of file entities in the POM.
  • Follow the other rules above - avoid including resources outside of the base directory of the project, and don't use properties customised to a particular environment.

One helpful method to assist reproducibility, if your environment has the capacity, is to set up a user on a machine with a clean environment that matches the target environment, with known properties, plugins and other software. This user can be used to build releases from a known baseline.

Scripting

Try to minimise the amount of scripting done. Those familiar with Ant will often put a lot of Ant tasks into maven.xml. You should try not to have a maven.xml file.

The reason for this is that this starts to make each build behave differently, losing one of the benefits of having a uniform environment. It also requires more maintenance and documentation.

Try to look for plugins that provide the functionality you are looking for, configure existing plugins, or even make small rearrangements to your project if you are just looking to reproduce existing functionality in a different way.

If you do have to do scripting, it is highly recommended to create a separate plugin project that provides these services so that it does not need to be duplicated across projects and the changes are isolated.

Things that are often placed in maven.xml are shortcut names for commonly used goal combinations, specification of the default goal, and pre/postGoal definitions to make small modifications to the behaviour of an existing goal - for example to add a new source root before compilation.

Writing Plugins

Writing plugins is the best way to extend Maven and continue to share it among other projects. However due to its architecture there are some things that are not recommended.

Firstly, try not to use pre/postGoal definitions inside a plugin. This will bind that plugin to another plugin causing side effects when the plugin is loaded, but different effects when it is not. A better solution is to define the behaviour in a new goal, and then have the projects using the plugin define the pre/postGoal to call the new goal.

Also, try not to call into other plugins using attainGoal. This makes your plugin susceptible to changes in the other plugin. Often you won't have an option - however the recommended way for plugins to expose their functionality is using Jelly Tag Libraries, or even better - shared Java code.

Use of prereqs is strongly preferred over attainGoal in any situation other than inside pre/postGoal definitions. This ensures the best order can be determined and that goals are only executed once.

Project Development Cycle

Whenever you start making changes after a recent release, you should check the currentVersion tag in project.xml and ensure that it is nextVersion-SNAPSHOT.

By tagging the version as -SNAPSHOT it signifies it is not released (so won't accidentally overwrite the official release), and has the advantage that when published, newer versions will be downloaded if it changes.

Part of the release process is to set currentVersion to the actual version released. For more information, set Making Releases.

Keep the Site Documentation Updated

The xdocs directory should contain a set of documentation that can be published as a site, giving users and developers instructions on how to use the project.

When you make changes to the project, make sure the documentation is kept up to date. For plugins, consider especially the xdocs/goals.xml and xdocs/properties.xml files. If the plugin has no xdocs, you can generate skeletons using maven plugin:generate-docs.

Maintain a Change Log

The xdocs/changes.xml file can be used to track changes as they are made, including who made them, what ticket number they relate to, and who they were contributed by if it was submitted as a patch.

Maintaining this gives a more readable change log than that provided by the SCM reports, and can be used to generate documentation and release notes.

When committing a change, you should edit and commit xdocs/changes.xml (create it if it does not exist) and describe the change according to the format given in the changes plugin.

Note: In the future, Maven should be able to integrate tightly with your issue tracking system to reduce the need for this.

Refactor Your Build

You will find that it is much easier to work with Maven when projects are simple and independent. This kind of loosely coupled structuring is often recommended for source code, so it makes sense to apply the same thought process to the structure of your build.

Don't be afraid to split an artifact in two if it is becoming complicated to maintain the build.

Some specific examples:

  • If you have some sets of integration or functional tests that rely on the artifact being fully packaged and have a large amount of configuration and/or code of their own, consider putting them in a separate project that depends on the main artifact.
  • If you need to share test code among different projects, put that test code in its own artifact (this time included in the main source tree) and depend on that artifact for your tests in other projects.
  • If you are generating a large amount of code, you may find it helpful to keep the generated code in an artifact of its own, and depend on that from your other projects.

Getting ready for Maven 2

Dependency Scopes: Maven 2 has a concept of scopes which are used to control when and where the dependencies are used in terms of e.g. the compile, test and runtime class paths. Maven 1 doesn't have this concept but it's still useful to include this information when adding dependencies to your project. It will be useful when you (possibly) convert your project to Maven 2 and if your repository is ever converted to a Maven 2 repository the Maven 2 POMs will include dependencies with the correct scope.

To set the scope on your dependency use the scope tag like this:

<dependency>
  <groupId>foo</groupId>
  <artifactId>bar</artifactId>
  <version>1.0</version>
  <properties>
    <scope>test</scope>
  </properties>
</dependency>