ZetCode

Maven archetypes

last modified November 8, 2026

In this article we show how to generate Maven projects from archetypes and how to write our own archetype. The examples use Maven 3.10, the Archetype plugin 3.4.1, and Java 25.

An archetype is a project template. It is distributed as a JAR file in a Maven repository, like any other artifact, and contains the files of the new project together with a descriptor. The archetype:generate goal downloads the archetype, asks for the values of its properties, such as the groupId and artifactId, and writes a new project into the current directory.

Archetypes are useful for starting projects that follow the same conventions: the same Java version, test framework, plugin versions, and directory layout. Many frameworks publish archetypes, and a team can publish its own.

Listing archetypes

Without parameters, archetype:generate runs in interactive mode and lists all archetypes from the Maven Central catalog, which has thousands of entries. The -Dfilter option narrows the list; its value is matched against groupId:artifactId. Here we list the archetypes published by the Maven project.

The plugin then waits for a number or a further filter; we press Ctrl+C to leave. Several of these archetypes are outdated. The ones still in use are maven-archetype-quickstart for a Java application or library, maven-archetype-webapp for a web application, maven-archetype-plugin for a Maven plugin, and maven-archetype-archetype for a new archetype.

$ mvn archetype:generate -Dfilter=org.apache.maven.archetypes:
Choose archetype:
1: remote -> org.apache.maven.archetypes:maven-archetype-archetype (An archetype which contains a sample archetype.)
2: remote -> org.apache.maven.archetypes:maven-archetype-j2ee-simple (An archetype which contains a simplified sample J2EE application.)
3: remote -> org.apache.maven.archetypes:maven-archetype-marmalade-mojo (-)
4: remote -> org.apache.maven.archetypes:maven-archetype-mojo (An archetype which contains a sample a sample Maven plugin.)
5: remote -> org.apache.maven.archetypes:maven-archetype-plugin (An archetype which contains a sample Maven plugin.)
6: remote -> org.apache.maven.archetypes:maven-archetype-plugin-site (An archetype which contains a sample Maven plugin site. This archetype can be layered upon an
    existing Maven plugin project.)
7: remote -> org.apache.maven.archetypes:maven-archetype-portlet (An archetype which contains a sample JSR-268 Portlet.)
8: remote -> org.apache.maven.archetypes:maven-archetype-profiles (-)
9: remote -> org.apache.maven.archetypes:maven-archetype-quickstart (An archetype which contains a sample Maven project.)
10: remote -> org.apache.maven.archetypes:maven-archetype-simple (An archetype which contains a simple Maven project.)
11: remote -> org.apache.maven.archetypes:maven-archetype-site (An archetype which contains a sample Maven site which demonstrates some of the supported document types like
    APT, XDoc, and FML and demonstrates how to i18n your site. This archetype can be layered
    upon an existing Maven project.)
12: remote -> org.apache.maven.archetypes:maven-archetype-site-simple (An archetype which contains a sample Maven site.)
13: remote -> org.apache.maven.archetypes:maven-archetype-site-skin (An archetype which contains a sample Maven Site Skin.)
14: remote -> org.apache.maven.archetypes:maven-archetype-webapp (An archetype which contains a sample Maven Webapp project.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains):
...

The quickstart archetype

In batch mode, we pass all values as properties and add -DinteractiveMode=false, or the -B option, so that Maven does not ask anything. The archetypeGroupId defaults to org.apache.maven.archetypes, so we can omit it for the quickstart archetype.

Always give -DarchetypeVersion. Without it, the plugin uses version 1.0 of the quickstart archetype, which generates a project with JUnit 3. Version 1.5 has two properties of its own: javaCompilerVersion, which defaults to 17, and junitVersion, which defaults to 5.11.0. The package property defaults to the groupId, and the version to 1.0-SNAPSHOT.

The generated project builds right away. Besides the sources, it contains the .mvn directory with empty maven.config and jvm.config files; they mark the root of the project and can hold default command line and JVM options. The generated POM pins the versions of all lifecycle plugins, but these are the versions from the time the archetype was released, 2024, so it is worth updating them.

$ mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart \
  -DarchetypeVersion=1.5 -DgroupId=com.example -DartifactId=demo \
  -DjavaCompilerVersion=25 -DjunitVersion=5.14.4 \
  -DinteractiveMode=false
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: demo
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example
[INFO] Parameter: packageInPathFormat, Value: com/example
[INFO] Parameter: junitVersion, Value: 5.14.4
[INFO] Parameter: javaCompilerVersion, Value: 25
[INFO] Project created from Archetype in dir: /home/user/demo
[INFO] BUILD SUCCESS
...
$ tree -a demo
demo
├── .mvn
│   ├── jvm.config
│   └── maven.config
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── example
    │               └── App.java
    └── test
        └── java
            └── com
                └── example
                    └── AppTest.java

10 directories, 5 files
$ cd demo
$ mvn package
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.034 s -- in com.example.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] Building jar: /home/user/demo/target/demo-1.0-SNAPSHOT.jar
[INFO] BUILD SUCCESS
...

Archetype templates and JUnit 6

The files of an archetype are Velocity templates. The quickstart POM template decides which JUnit dependencies to add by looking at the start of the junitVersion value.

#macro( junit $ver )
#if ( $ver.startsWith("4.") )
  <dependencies>
    ...
#elseif ( $ver.startsWith("5.") )
  <dependencyManagement>
    ...
#end
#end

The template knows only JUnit 4 and JUnit 5. With -DjunitVersion=6.1.3, the generation succeeds, but the POM gets no test dependency at all, while the generated test class still uses the JUnit Jupiter API. The build fails when it compiles the tests. With quickstart 1.5, use a 5.x version and change the BOM version in the POM to 6.x afterwards, or use your own archetype.

$ grep -c junit pom.xml
0
$ mvn test
[ERROR] /home/user/demo/src/test/java/com/example/AppTest.java:[3,36] package org.junit.jupiter.api does not exist
[ERROR] /home/user/demo/src/test/java/com/example/AppTest.java:[5,29] package org.junit.jupiter.api does not exist
[ERROR] BUILD FAILURE
...

Archetype structure

A custom archetype is a Maven project with the maven-archetype packaging. Our greeter archetype generates a Java 25 console application with a JUnit 6 test.

The archetype-resources directory holds the files of the generated project, archetype-metadata.xml describes them, and the projects directory contains integration tests of the archetype. The archetype has the following layout:

greeter-archetype
├── pom.xml
└── src
    ├── main/resources
    │   ├── META-INF/maven/archetype-metadata.xml
    │   └── archetype-resources
    │       ├── pom.xml
    │       ├── README.md
    │       └── src
    │           ├── main/java/App.java
    │           └── test/java/AppTest.java
    └── test/resources/projects/basic
        ├── archetype.properties
        └── goal.txt

The archetype POM

The POM of the archetype project is short.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.example</groupId>
    <artifactId>greeter-archetype</artifactId>
    <version>1.0.0</version>
    <packaging>maven-archetype</packaging>

    <description>Creates a Java 25 console application with JUnit 6</description>

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

The maven-archetype packaging is not built into Maven; it is provided by the archetype-packaging build extension. The extension binds the Archetype plugin goals to the lifecycle: the jar goal packages the archetype, the integration-test goal tests it, and the update-local-catalog goal registers it in the local catalog on install. The description is shown in archetype lists.

The archetype descriptor

The archetype-metadata.xml file declares the properties of the archetype and the files it generates.

src/main/resources/META-INF/maven/archetype-metadata.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="greeter">

    <requiredProperties>
        <requiredProperty key="greeting">
            <defaultValue>Hello</defaultValue>
        </requiredProperty>
        <requiredProperty key="javaVersion">
            <defaultValue>25</defaultValue>
            <validationRegex>\d+</validationRegex>
        </requiredProperty>
    </requiredProperties>

    <fileSets>
        <fileSet filtered="true" packaged="true">
            <directory>src/main/java</directory>
        </fileSet>
        <fileSet filtered="true" packaged="true">
            <directory>src/test/java</directory>
        </fileSet>
        <fileSet filtered="true">
            <directory></directory>
            <includes>
                <include>README.md</include>
            </includes>
        </fileSet>
    </fileSets>
</archetype-descriptor>

The properties groupId, artifactId, version, and package are always available. Our archetype adds two more.

<requiredProperty key="greeting">
    <defaultValue>Hello</defaultValue>
</requiredProperty>

A required property with a default value does not have to be given; the default is used. A required property without a default must be given on the command line in batch mode.

<validationRegex>\d+</validationRegex>

The validationRegex element checks the value. The check is done only when the value is typed in interactive mode; in batch mode, an invalid value such as -DjavaVersion=latest passes unchecked into the generated POM.

<fileSet filtered="true" packaged="true">
    <directory>src/main/java</directory>
</fileSet>

A file set selects files from archetype-resources. The filtered attribute means that the files are processed as Velocity templates. The packaged attribute means that the files are placed into the directory of the package: App.java ends up in src/main/java/org/acme/App.java for the org.acme package.

<fileSet filtered="true">
    <directory></directory>
    <includes>
        <include>README.md</include>
    </includes>
</fileSet>

An empty directory refers to the root of the generated project. The POM template does not need a file set; it is always processed.

The templates

The templates use the ${name} syntax to insert property values.

src/main/resources/archetype-resources/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>${groupId}</groupId>
    <artifactId>${artifactId}</artifactId>
    <version>${version}</version>

    <properties>
        <maven.compiler.release>${javaVersion}</maven.compiler.release>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>6.1.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.16.0</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>3.6.1</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.5.1</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

The POM template fills in the coordinates and the Java release, and sets up JUnit 6 with its BOM and current plugin versions.

src/main/resources/archetype-resources/src/main/java/App.java
package ${package};

public class App {

    static String greet(String name) {
        return "${greeting}, " + name + "!";
    }

    public static void main(String[] args) {
        String name = args.length > 0 ? args[0] : "${artifactId}";
        System.out.println(greet(name));
    }
}

The application greets the name given as an argument; without one, it greets the name of the project. The greeting property becomes part of the source code.

src/main/resources/archetype-resources/src/test/java/AppTest.java
package ${package};

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class AppTest {

    @Test
    void greetsByName() {
        assertEquals("${greeting}, Maven!", App.greet("Maven"));
    }
}

The test uses the same property, so it matches the generated code.

Velocity treats $ and # specially. Every ${name} that matches an archetype property is replaced, so a template that must produce a literal Maven expression such as ${version} has to escape it. The convention, also used by the plugin itself, is a helper variable: #set( $symbol_dollar = '$' ) at the top of the file and ${symbol_dollar}{version} in the text.

Testing the archetype

Each directory under src/test/resources/projects is one test case. The archetype.properties file holds the values for the generation.

src/test/resources/projects/basic/archetype.properties
groupId=org.acme
artifactId=basic
version=0.1.0
package=org.acme.basic
greeting=Hi
javaVersion=25

The goal.txt file names the goals run on the generated project; ours contains a single word, package. So the test generates a project and builds it, including its unit test.

The install phase runs the integration test, installs the archetype into the local repository, and adds it to the local catalog in ~/.m2/repository/archetype-catalog.xml. Archetypes from the local catalog are listed by mvn archetype:generate -DarchetypeCatalog=local.

$ mvn install
[INFO] --- resources:3.3.1:resources (default-resources) @ greeter-archetype ---
[INFO] --- resources:3.3.1:testResources (default-testResources) @ greeter-archetype ---
[INFO] --- archetype:3.4.1:jar (default-jar) @ greeter-archetype ---
[INFO] --- archetype:3.4.1:integration-test (default-integration-test) @ greeter-archetype ---
[INFO] [INFO] Running org.acme.basic.AppTest
[INFO] [INFO] BUILD SUCCESS
[INFO] Post-archetype-generation invoker exit code: 0
[INFO] --- install:3.1.4:install (default-install) @ greeter-archetype ---
[INFO] --- archetype:3.4.1:update-local-catalog (default-update-local-catalog) @ greeter-archetype ---
[INFO] Updated local archetypes catalog /home/user/.m2/repository/archetype-catalog.xml
[INFO] BUILD SUCCESS
...
$ cat ~/.m2/repository/archetype-catalog.xml
<?xml version="1.0" encoding="UTF-8"?>
<archetype-catalog xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0 https://maven.apache.org/xsd/archetype-catalog-1.0.0.xsd"
    xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <archetypes>
    <archetype>
      <groupId>com.example</groupId>
      <artifactId>greeter-archetype</artifactId>
      <version>1.0.0</version>
      <description>Creates a Java 25 console application with JUnit 6</description>
    </archetype>
  </archetypes>
</archetype-catalog>

Using the archetype

We generate a project from the installed archetype. The greeting property is set on the command line; the javaVersion property takes its default value.

The Slovak greeting Ahoj was written into the source code, and the project name hello into the fallback name. To share the archetype with others, deploy it to a shared repository with mvn deploy; users then need only its coordinates.

$ mvn archetype:generate -DarchetypeGroupId=com.example \
  -DarchetypeArtifactId=greeter-archetype -DarchetypeVersion=1.0.0 \
  -DgroupId=org.acme -DartifactId=hello -Dgreeting=Ahoj \
  -DinteractiveMode=false
[INFO] Parameter: groupId, Value: org.acme
[INFO] Parameter: artifactId, Value: hello
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: org.acme
[INFO] Parameter: packageInPathFormat, Value: org/acme
[INFO] Parameter: javaVersion, Value: 25
[INFO] Parameter: greeting, Value: Ahoj
[INFO] Project created from Archetype in dir: /home/user/hello
[INFO] BUILD SUCCESS
...
$ cd hello
$ cat src/main/java/org/acme/App.java
package org.acme;

public class App {

    static String greet(String name) {
        return "Ahoj, " + name + "!";
    }

    public static void main(String[] args) {
        String name = args.length > 0 ? args[0] : "hello";
        System.out.println(greet(name));
    }
}
$ mvn -q package
$ java -cp target/hello-1.0-SNAPSHOT.jar org.acme.App Maven
Ahoj, Maven!

Interactive mode

In interactive mode, the plugin asks for each property that has no value; a value in the prompt is the default, taken when we press Enter. Then it asks for a confirmation. If we answer N, it asks for all the values again; the archetype properties keep their current values as defaults, but the groupId and artifactId must be typed again. Here we replace the greeting and try an invalid Java version, which the validation regex rejects.

$ mvn archetype:generate -DarchetypeGroupId=com.example \
  -DarchetypeArtifactId=greeter-archetype -DarchetypeVersion=1.0.0
[INFO] Using property: greeting = Hello
[INFO] Using property: javaVersion = 25
Define value for property 'groupId': org.acme
Define value for property 'artifactId': hi
Define value for property 'version' 1.0-SNAPSHOT: 
Define value for property 'package' org.acme: 
Confirm properties configuration:
greeting: Hello
javaVersion: 25
groupId: org.acme
artifactId: hi
version: 1.0-SNAPSHOT
package: org.acme
 Y: N
Define value for property 'greeting' Hello: Hey
Define value for property 'javaVersion' (should match expression '\d+') 25: latest
Value does not match the expression, please try again 25: 21
Define value for property 'groupId': org.acme
Define value for property 'artifactId': hi
Define value for property 'version' 1.0-SNAPSHOT: 
Define value for property 'package' org.acme: 
Confirm properties configuration:
greeting: Hey
javaVersion: 21
groupId: org.acme
artifactId: hi
version: 1.0-SNAPSHOT
package: org.acme
 Y: Y
[INFO] Project created from Archetype in dir: /home/user/hi

Creating an archetype from a project

Instead of writing an archetype by hand, we can derive it from an existing project with the archetype:create-from-project goal. Our starter project is an ordinary project with an application class and a test.

The goal writes a complete archetype project into target/generated-sources/archetype and then builds it. In version 3.4.1 of the plugin, the build step fails with The specified user settings file does not exist when there is no ~/.m2/settings.xml file. Passing any settings file with -s avoids the problem; we use one that contains only <settings/>.

The plugin replaced the com.example.starter package with ${package} and the starter name with ${artifactId}, and added Velocity definitions that protect $ and # characters. The generated archetype is a starting point: we review the templates, add required properties, and then install it from the target/generated-sources/archetype directory with mvn install.

$ mvn archetype:create-from-project -s ../settings.xml
[INFO] Building starter 1.0.0
[INFO] --- archetype:3.4.1:create-from-project (default-cli) @ starter ---
[INFO] Building starter-archetype 1.0.0
[INFO] --- resources:3.3.1:resources (default-resources) @ starter-archetype ---
[INFO] --- resources:3.3.1:testResources (default-testResources) @ starter-archetype ---
[INFO] --- archetype:3.4.1:jar (default-jar) @ starter-archetype ---
[INFO] Building archetype jar: /home/user/starter/target/generated-sources/archetype/target/starter-archetype-1.0.0.jar
[INFO] Building jar: /home/user/starter/target/generated-sources/archetype/target/starter-archetype-1.0.0.jar
[INFO] BUILD SUCCESS
[INFO] BUILD SUCCESS
...
$ cd target/generated-sources/archetype
$ find . -type f | sort
./pom.xml
./src/main/resources/META-INF/maven/archetype-metadata.xml
./src/main/resources/archetype-resources/pom.xml
./src/main/resources/archetype-resources/src/main/java/App.java
./src/main/resources/archetype-resources/src/test/java/AppTest.java
./src/test/resources/projects/basic/archetype.properties
./src/test/resources/projects/basic/goal.txt
./target/classes/META-INF/maven/archetype-metadata.xml
./target/classes/archetype-resources/pom.xml
./target/classes/archetype-resources/src/main/java/App.java
./target/classes/archetype-resources/src/test/java/AppTest.java
./target/maven-archiver/pom.properties
./target/starter-archetype-1.0.0.jar
./target/test-classes/projects/basic/archetype.properties
./target/test-classes/projects/basic/goal.txt
$ head -4 src/main/resources/archetype-resources/src/main/java/App.java
#set( $symbol_pound = '#' )
#set( $symbol_dollar = '$' )
#set( $symbol_escape = '\' )
package ${package};

Source

Maven Archetype Plugin

In this article we have generated projects from Maven archetypes and created our own archetype.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Java tutorials.