Manually creating a new Maven project is cumbersome. Most companies have a set of shared libraries (e.g., security, encryption, or payment) and pre-defined configurations they need for each project. Copy and pasting these files over and over is a tedious task. With Maven, we have the option to build project templating toolkits with so-called Maven Archetypes. This blog post will teach you how to create your own Maven Archetype in 5 simple steps.
As an example for this blog post, we create a minimal Jakarta EE 8 with Java 11 Maven Archetype.
Maven Archetype Project Setup
Creating a custom Maven Archetype is simple.
To create a Maven Archetype, we use – who guessed – Maven. This Maven project requires the following setup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <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>de.rieckpil.archetypes</groupId> <artifactId>custom-maven-archetype</artifactId> <version>1.0.0</version> <name>Custom Maven Archetype</name> <packaging>maven-archetype</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <archetype-packaging.version>3.2.1</archetype-packaging.version> </properties> <build> <extensions> <extension> <groupId>org.apache.maven.archetype</groupId> <artifactId>archetype-packaging</artifactId> <version>${archetype-packaging.version}</version> </extension> </extensions> </build> </project> |
Compared to everyday Maven projects (e.g., a Spring Boot microservice), this project setup is a bit different.
First, the packaging
type is maven-archetype
. While our day-to-day Java services are packaged as a war
or jar
files, we use the packaging maven-archetype
for creating a custom archetype.
Next, we don't need any dependency or plugin but a Maven extension: archetype-packaging
. This extension will build our final archetype later on.
Defining Our Project Template
The main goal of our archetype is to scaffold a new Maven project with common resource files, Java classes, and dependencies.
We define the raw template Maven project as part of src/main/resources/archetype-resources
.
Within this folder, we specify the target project like a usual Maven project. Here we have to set up the folder structure of our target project and include all files we want.
For our Jakarta EE 8 project, this might look like the following:
We can add any file we like and are not limited to Java classes, e.g., a Dockerfile
, .gitignore
, .editorconfig
, etc.
To instruct Maven on which files to include in the archetype, we need to create a archetype-metadata.xml
file within src/main/resources/META-INF/maven
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?xml version="1.0" encoding="UTF-8"?> <archetype-descriptor xmlns="https://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.1.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.1.0 https://maven.apache.org/xsd/archetype-descriptor-1.1.0.xsd" name="custom-maven-archetype"> <fileSets> <fileSet filtered="true" packaged="true" encoding="UTF-8"> <directory>src/main/java</directory> </fileSet> <fileSet filtered="true" packaged="true" encoding="UTF-8"> <directory>src/test/java</directory> </fileSet> <fileSet filtered="true" encoding="UTF-8"> <directory>src/main/resources</directory> </fileSet> <fileSet filtered="true" encoding="UTF-8"> <directory>src/main/webapp</directory> </fileSet> <fileSet filtered="true" encoding="UTF-8"> <directory></directory> <includes> <include>.gitignore</include> <include>README.md</include> <include>Dockerfile</include> <include>server.xml</include> <include>myScript.sh</include> </includes> </fileSet> </fileSets> </archetype-descriptor> |
Each fileSet
describes a set of files or directories we want to include within our Maven Archetype.
The attribute filtered
defines whether or not Maven should replace placeholders inside the file. Those placeholders let us define dynamic text inside our files, e.g., the package name of a Java class.
For a detailed explanation of the available elements and attributes in this archetype meta file, have a look at the official documentation.
Adding Placeholders for Our Files
To make the resulting project and its files more dynamic, we can add placeholders to our files.
Once we use the archetype, we will specify the groupId
, artifactId
and version
of our project. These values can be injected into any file we like as the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <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>${groupId}</groupId> <artifactId>${artifactId}</artifactId> <version>${version}</version> <packaging>war</packaging> <!-- more --> <build> <finalName>${artifactId}</finalName> </build> </project> |
For our demo archetype, we also use these variables to prepare a Dockerfile and bash script:
1 2 3 | FROM open-liberty:kernel-java11 COPY --chown=1001:0 target/${artifactId}.war /config/dropins/ COPY --chown=1001:0 server.xml /config |
1 2 3 | #!/bin/sh mvn package && docker build -t ${groupId}/${artifactId} . docker rm -f ${artifactId} || true && docker run -d -p 9080:9080 -p 9443:9443 --name ${artifactId} ${groupId}/${artifactId} |
Please note that this variable replacement only takes place for files or directories we marked as filtered
in the archetype-metadata.xml
file.
Similarly, if our archetype contains pre-constructed Java classes, we might want to place them in the correct package structure. We can achieve this by setting the packaged
attribute in archetype-metadata.xml
to true and use ${package}
placeholder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package ${package}; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.core.Response; @Path("sample") public class SampleResource { @GET public Response message() { String message = "Hello World from ${groupId}/${artifactId}"; return Response.ok(message).build(); } } |
If placeholders and the packaging option are not enough, we can add an archetype-post-generate.groovy
script within src/main/resources/META-INF
.
This file gets executed after Maven successfully creates a new project out of our archetype. With this file, we can do any customization to the resulting project you want.
As an example, we'll use this file to mark the bash script as an executable file:
1 2 | def file = new File(request.getOutputDirectory(), request.getArtifactId() + "/myScript.sh") file.setExecutable(true, false) |
Publish Our Maven Archetype
Now we are ready to publish the Maven Archetype. For a company's internal archetype, we might choose our internal artifact manager.
We can also publish it to the Sonatype Nexus Repository Manager to make it available for everyone.
For a first local demo, we can publish the archetype to our local Maven repository first:
1 | ./mvnw install |
This will install the archetype in our local repository (~/.m2/repository
).
Using the Archetype to Bootstrap Projects Faster
Finally, we can now make use of the archetype. Maven offers a plugin for this, which we can use with the following command:
1 2 3 4 5 6 7 | mvn archetype:generate \ -DarchetypeGroupId=de.rieckpil.archetypes \ -DarchetypeArtifactId=custom-maven-archetype \ -DarchetypeVersion=1.0.0 \ -DgroupId=de.rieckpil.blog \ -DartifactId=new-project \ -DinteractiveMode=false |
The users of our Maven Archetype just need to specify the correct groupId
and artifactId
, that's all.
The source code for this Maven Archetype is available on GitHub.
Furthermore, I maintain a set of custom Maven archetypes that you can use as a source of inspiration (they are all published to Sonatype). There you'll also find a blueprint on how to use GitHub Actions to automate the Sonatype Nexus Repository OSS release part.
Have fun creating your own Maven Archetype,
Philip