Unleashing the Power of Maven Archetypes

Creating new projects from scratch is a common task in today’s world. Often, this involves manually setting up folders and files or using tools like Spring Initializr, Micronaut Launch, Eclipse Starter for Jakarta EE, Quarkus Starter, or IntelliJ to streamline the initial setup. But what if there was a way to automate and standardise these tasks, taking your project setup to the next level?

While these tools are excellent for generating a basic “Hello World” application, they often fall short when it comes to adding project-specific configurations and components that are standard within your organisation. As a result, you might find yourself spending time in a tedious and error-prone process such as copy-pasting parts of similar applications, renaming files, and replacing variables.

Introduction

For those unfamiliar with Maven, it is one of the most widely used project management tools in the Java ecosystem. Maven manages a project’s build, reporting, and documentation from a central configuration. It operates through plugins that hook into its lifecycle phases, although some plugin goals can be used standalone.

One of those plugins is the Maven Archetype Plugin, which you might have encountered when following the official Getting Started Guide. Officially supported by the Maven Project, this plugin allows developers to create and use archetypes. Archetypes are original patterns or models from which other projects of the same kind are made. It is an abstract representation of a kind of project that can be instantiated into a concrete customised project.

Using the Maven Archetype Plugin, developers can quickly and consistently generate new projects. This not only boosts productivity but also ensures that the generated components adhere to best practices, guidelines, and standards within your organisation, while eliminating repetitive and tedious tasks.

How Maven Archetypes work

Before diving into creating an archetype, it’s important to understand how they work. Like any other dependency, archetypes are typically hosted in remote or local repositories, using the same configuration as dependencies for resolution. If an archetype is not managed by a repository, you must add an additional repository entry with the ID archetype in the settings.xml file.

The Maven Archetype Plugin discovers available archetypes through a catalog file. This catalog stores information about archetypes, including their GAV (GroupId, ArtifactId, and Version), the repository they are hosted in, and their description. If the repository is omitted, the same repository as the catalog is used to locate the archetype.

The plugin comes bundled with several predefined archetypes, which can be displayed using the command: mvn archetype:generate -DarchetypeCatalog=internal. This command uses the internal catalog. Besides the internal catalog, there is also a local catalog found in the home directory at ~/.m2/archetype-catalog.xml, and a remote catalog accessible at https://repo.maven.apache.org/maven2/archetype-catalog.xml . They can be specifically targeted using remote or local as the value of the archetypeCatalog parameter.

Archetypes are packaged in a JAR file that contains a descriptor. This descriptor includes metadata about the archetype, such as required properties for project generation, possible default values, and the files to include and generate. The following image gives a brief overview of this process.

overview

Generating a project from an archetype can be done interactively or non-interactively using additional command-line flags. The process involves selecting an archetype, entering additional configuration parameters or user input, and generating the project. The templating engine used, Velocity, expands the properties in the files and generates the dynamic content on the fly.

Creating an archetype

Maven Archetypes can be created from scratch or by using the archetype:create-from-project goal, which generates an archetype from an existing project but includes all files. This requires manual filtering of unwanted files afterwards. Alternatively, the maven-archetype-archetype, which is an archetype to generate a simple archetype, generates most of the setup but is outdated. In this example, we will build an archetype from scratch, though the same concepts apply to both methods.

Creating an archetype project involves a different setup compared to most Maven projects. Let’s assume the current working directory is ~/archetype. The process begins with creating a new pom.xml file and the src/main/ folders. Next, place the archetype descriptor in the src/main/resources/META-INF/archetype-metadata.xml file. The files that are copied and dynamically filled are placed in the ~/archetype/src/main/resources/archetype-resources folder. These archetype resources could be build scripts, YAML pipelines, Git ignores, Java classes, and any other standardised files for your organisation. The following image shows this archetype setup.

example-setup

Start by creating a new Maven project with a POM similar to the one:

<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>


 <groupId>com.giovds</groupId>
 <artifactId>my-first-archetype</artifactId>
 <version>1.0-SNAPSHOT</version>
 <packaging>maven-archetype</packaging>


 <build>
   <pluginManagement>
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-archetype-plugin</artifactId>
         <version>3.2.1</version>
       </plugin>
     </plugins>
   </pluginManagement>
  
   <extensions>
     <extension>
       <groupId>org.apache.maven.archetype</groupId>
       <artifactId>archetype-packaging</artifactId>
       <version>3.2.1</version>
     </extension>
   </extensions>
 </build>
</project>

Notice the use of a different packaging type. The archetype-packaging extension defines custom lifecycle bindings , allowing Maven to execute the appropriate plugin goals for this new packaging type.

The archetype-metadata.xml also known as the descriptor, has two attributes. The name describes the display name when the user selects the archetype. Partial indicates whether the archetype represents an entire project or only a part, such as a specific module or selection of classes. It also contains properties required to generate the archetype. The properties will be passed through the plugin and replaced by Velocity during project generation. This example shows some configuration options.

<archetype-descriptor
       xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.1.0"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.1.0 https://maven.apache.org/xsd/archetype-descriptor-1.1.0.xsd"
       name="first-archetype"
       partial="false">
   <requiredProperties>
      <requiredProperty key="service-name">
        <defaultValue>my-first-service</defaultValue>
      </requiredProperty>
      <requiredProperty key="team-name">
        <validationRegex>^[A-Z].*$</validationRegex>
      </requiredProperty>
   </requiredProperties>


   <fileSets>
      <fileSet encoding="UTF-8" filtered="true" packaged="true">
        <directory>src/main/java</directory>
      </fileSet>
      <fileSet encoding="UTF-8" filtered="true" packaged="false">
        <directory/>
        <includes>
          <include>**/*.yml</include>
        </includes>
        <excludes>
          <exclude>secret.config</exclude>
        </excludes>
      </fileSet>
      <fileSet encoding="UTF-8" filtered="false" packaged="false">
        <directory/>
        <includes>
          <include>README.md</include>
        </includes>
      </fileSet>
   </fileSets>
</archetype-descriptor>

The descriptor also configures which filesets to include and how to include them. The filtered attribute controls whether the files within this set contain properties that need to be replaced by Velocity. If set to false, the files are copied as-is. The packaged attribute places the files in a packaged structure, meaning files within this fileset will be placed in folders resembling a package-like structure. For example, if a project was generated with a groupId of com.giovds and the fileset directory to include was configuration, the files within would be placed in /my-service/configuration/com/giovds. This is mainly used for class files. The non-packaged files will be placed in the root folder /my-service.

To install and use the archetype, the archetype’s JAR must be deployed to your local repository using mvn install. Then, generate a new project by running mvn archetype:generate -DarchetypeCatalog=local. The command line flag tells the plugin we are only interested in locally installed archetypes, to make it easier to find the newly installed one.

Property interpolation with Velocity

Velocity templates in Maven Archetypes allow for dynamic content generation by utilising properties and custom logic.

For basic property replacement, Velocity properties use the same format as Maven properties, denoted as ${property}. Properties defined in the requiredProperties element of the archetype descriptor can be accessed this way. Additionally, GAV values can also be used. For example, in a template POM file within the archetype-resources folder, these properties can automatically fill the groupId, artifactId, and version.

However, when replacing properties in filenames, the property name is surrounded by two underscores on each side. For example, if there is a property service-name and a file needs to include this property in its name, it could be named __service-name__Application.java. Within this file, other properties would use the standard ${property} format, as shown here:

package ${groupId};

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.SpringApplication;

@SpringBootApplication
@EntityScan(basePackageClasses = ${service-name}Application.class)
public class ${service-name}Application {
   public static void main(final String[] args) {
       SpringApplication.run(${service-name}Application.class, args);
   }
}

In some cases, it’s useful to derive values from other properties. Velocity Template Language (VTL) statements allow for defining new variables that can be processed by the engine.

For example, consider a project that uses two different usernames for database access and migrations, following a naming convention like name_db_user and name_migration_user. If the archetype has a property named database-username filled by the user with a value like contract_db_user, a VTL statement can be used to derive the migration username instead of defining a separate property. The statement could split the value by an underscore and select the first and last parts to determine the migration username.

#set ($split = $database-username.split("_"))
#set ($splitLastIndex = $usernameSplit.size() - 1)
username: ${split[0]}_migration_${split[$splitLastIndex]}

This is one of the many examples of how VTL statements can be used to manipulate these properties. More information and advanced usages can be found in the Velocity User Guide .

Keeping archetypes up to date

Software evolves over time, and so do the decisions and requirements that shape it. Consequently, archetypes need to be updated to reflect these changes. Manually checking if an archetype still works can be tedious. Fortunately, the plugin provides tools to automate this process.

The integration-test goal of the plugin can be utilised to automate the testing of archetypes. This goal is bound to the Maven integration test phase by default. It generates projects from the current archetype and optionally compares these generated projects to a reference copy.

Each test is represented as a sub-directory in src/test/resources/projects. This directory should contain an archetype.properties file. This file contains the properties used to generate the project, just like a user would via the command line. It contains a goals.txt to specify none or more goals to run against the generated project. And an optional reference/ directory containing a copy of the expected project structure and content for comparison.

Since an archetype project is a Maven project itself, and possibly contains other projects that define dependencies, it can benefit from automated dependency management tools to stay up to date. Tools like Renovate and Dependabot can automate the process of updating the archetype project and its dependencies. In combination with the test suite the archetypes require minimal maintenance and will stay updated over time without unknowingly breaking.

Final thoughts and my experience with Archetypes

I have successfully introduced archetypes in two organisations, mostly receiving great feedback from the teams. They are actively used by the developers, enabling them to start development quickly without the hassle of copy-pasting projects, making unnoticed renaming errors, and recreating all the necessary application and deployment configurations.

However, before enthusiastically introducing archetypes to your team, consider the following questions:

  • How often do you need to create new services or partial components?
  • Do you have the necessary automation in place to test and update the archetypes?
  • How often do your project requirements change?

Creating and maintaining archetypes involves a learning curve, especially when working with Velocity templates. If the frequency of creating new components is low, the initial effort and continuous maintenance might not be justified. Without automated pipelines for updates and tests, the maintenance effort might outweigh the benefits. Additionally, frequent changes can impact the usefulness of archetypes since they are not backward compatible and cannot be re-applied to existing projects.

Ready to enhance your development workflow with Maven Archetypes? Start by assessing your team’s needs, set up automated pipelines if you haven’t already, and dive into the creation of your first archetype. The benefits of a more efficient and error-free setup process are just a few steps away. Don’t wait—begin your journey with Maven Archetypes today and empower your teams to focus on what they do best: writing great code.