Write your first Maven plugin 3 in Java¶
What you'll learn¶
In this tutorial you'll build a Maven 3 plugin in Java with a single goal that prints a greeting. By the end you'll have installed the plugin in your local repository and run it from the command line.
You will:
- Create a Maven 3 plugin project
- Write a Mojo with a custom goal
- Install the plugin locally and run it from the command line
- Optionally configure a Mojo parameter
Prerequisites¶
Before starting this tutorial, you should have:
- Maven 3.9 or newer installed and available on your
PATH(Installing Apache Maven) - A JDK that can compile Java 17 sources (JDK 17 or newer)
- Completed a basic Maven build at least once (Your first Maven build in 5 minutes)
You don't need to know anything about Maven plugins yet, that's what we're here to learn.
Step 1: Create the plugin project layout¶
Maven plugins use the same standard directory layout as other Java projects.
Create a folder for the plugin, the Java package path, and an empty pom.xml:
Verify: list the project tree for your platform. It should match the layout below.
Step 2: Declare a Maven plugin POM¶
Open hello-maven-plugin/pom.xml and give Maven the coordinates, packaging, and APIs this plugin needs:
<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>sample.plugin</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>Hello Maven Plugin</name>
<prerequisites>
<maven>3.9.0</maven>
</prerequisites>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.version>3.9.9</maven.version>
<maven.plugin.tools.version>3.15.2</maven.plugin.tools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${maven.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${maven.plugin.tools.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>${maven.plugin.tools.version}</version>
<executions>
<execution>
<id>help-mojo</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Here's what the important parts do:
maven-pluginpackaging applies the plugin build lifecycle, including plugin descriptor generation.<prerequisites><maven>declares the minimum Maven version this plugin requires.maven-plugin-apiprovidesAbstractMojoand related Maven 3 APIs.maven-plugin-annotationsprovides@Mojoand@Parameter.providedscope keeps those APIs off the plugin JAR because Maven supplies them at runtime.- Pinning
maven-plugin-plugininpluginManagementsets which Plugin Tools version that lifecycle uses for descriptor generation. - The
helpmojoexecution generates ahelpgoal for your plugin during the build.
Plugin naming and the Maven trademark
Name community plugins hello-maven-plugin (the ${prefix}-maven-plugin convention).
Avoid maven-${prefix}-plugin.
That naming pattern is reserved for official Apache Maven plugins with groupId org.apache.maven.plugins.
Verify: from hello-maven-plugin/, run:
You should see BUILD SUCCESS.
If you see an unknown packaging type: confirm <packaging>maven-plugin</packaging> is spelled exactly that way.
Step 3: Write your first Mojo¶
A Mojo is the Java implementation of a Maven goal.
A plugin can package one or more Mojos.
Create src/main/java/sample/plugin/GreetingMojo.java:
package sample.plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
/**
* Says "Hi" to the user.
*/
@Mojo(name = "sayhi")
public class GreetingMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException {
getLog().info("Hello, world.");
}
}
Let's walk through what this does.
Extending AbstractMojo gives you logging and the rest of the goal infrastructure.
You implement execute().
@Mojo(name = "sayhi") registers this class as the sayhi goal.
getLog().info(...) writes a user-visible message.
Throwing MojoExecutionException fails the build with BUILD FAILURE.
Verify: from hello-maven-plugin/, compile the Mojo:
You should see BUILD SUCCESS.
If compilation fails on AbstractMojo or @Mojo: check that both dependencies from Step 2 are inside <dependencies> and that their versions resolve from Maven Central.
Step 4: Install the plugin¶
Install the plugin into your local repository so other projects (and the command line) can resolve it:
A successful run ends with BUILD SUCCESS and installs sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:
[INFO] --- maven-install-plugin:...:install (default-install) @ hello-maven-plugin ---
[INFO] Installing .../hello-maven-plugin-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Verify: confirm the descriptor in the build output and the install in your local repository:
ls target/classes/META-INF/maven/plugin.xml
ls ~/.m2/repository/sample/plugin/hello-maven-plugin/1.0-SNAPSHOT/
plugin.xml lives under target/classes (and inside the plugin JAR).
It is not a separate file in ~/.m2.
In the local repository directory you should see hello-maven-plugin-1.0-SNAPSHOT.jar (and the installed POM).
If you see No plugin descriptor found later when invoking the goal: re-run mvn clean install and check that target/classes/META-INF/maven/plugin.xml exists after the build.
Step 5: Run the sayhi goal¶
After install, the plugin is available from any Maven project, not only from hello-maven-plugin/.
From a directory that contains a pom.xml, invoke the goal with its full coordinates:
A successful run prints the greeting and ends in BUILD SUCCESS:
[INFO] --- hello:1.0-SNAPSHOT:sayhi (default-cli) @ ... ---
[INFO] Hello, world.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
The coordinate form is groupId:artifactId:version:goal.
For predictable results in this tutorial, keep the version in the command.
You can omit the version and let Maven resolve which plugin version to use:
If Maven cannot resolve the plugin: confirm Step 4 finished with BUILD SUCCESS, then check ~/.m2/repository/sample/plugin/hello-maven-plugin/1.0-SNAPSHOT/ for the installed JAR and POM.
Step 6: Add a configurable parameter¶
The goal works, but it always prints the same text.
Add a greeting parameter so callers can change the message:
package sample.plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
/**
* Says "Hi" to the user.
*/
@Mojo(name = "sayhi")
public class GreetingMojo extends AbstractMojo {
/**
* The greeting to display.
*/
@Parameter(property = "sayhi.greeting", defaultValue = "Hello, world.")
private String greeting;
@Override
public void execute() throws MojoExecutionException {
getLog().info(greeting);
}
}
@Parameter marks the field as Mojo configuration.
defaultValue is used when the caller does not set the parameter.
property exposes a user property so you can override it with -D on the command line.
Reinstall, then run with a custom greeting:
mvn install
mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi -Dsayhi.greeting="Hello, Maven plugin developers!"
Verify: the log contains:
Step 7: Shorten the command line (optional)¶
Typing the full coordinates every time gets old quickly.
The artifactId hello-maven-plugin follows the ${prefix}-maven-plugin convention, so Maven derives the prefix hello.
If a project declares the plugin under <build><plugins> or <build><pluginManagement>, that project can resolve the hello prefix from those declarations.
Outside that, Maven does not search arbitrary plugin groupIds for prefix resolution unless they are listed in pluginGroups.
Add sample.plugin to ~/.m2/settings.xml so the hello prefix can resolve without declaring the plugin in every project.
Add this block to your existing settings file. Do not replace the whole file:
<settings>
<!-- ... -->
<pluginGroups>
<pluginGroup>sample.plugin</pluginGroup>
</pluginGroups>
</settings>
Then run:
Verify: the log contains:
If you see No plugin found for prefix 'hello': the current project does not declare the plugin, and Maven is only searching the default plugin groups.
Confirm sample.plugin is listed under <pluginGroups> in the settings file Maven is using, or declare the plugin in the project's <build><plugins> / <pluginManagement>.
Step 8: Configure the parameter in the POM (optional)¶
You can set the same parameter in pom.xml instead of -D.
Add a <plugins> block inside the existing <build> section, next to <pluginManagement>:
<build>
<pluginManagement>
<!-- ... unchanged ... -->
</pluginManagement>
<plugins>
<plugin>
<groupId>sample.plugin</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<greeting>Welcome</greeting>
</configuration>
</plugin>
</plugins>
</build>
You can put the same <configuration> under <pluginManagement> instead when you want shared defaults without adding the plugin to <plugins> yet.
The configuration element name (greeting) matches the Mojo field name by default.
You can change that XML name with @Parameter(name = "...") on the field.
Verify: from a project that has this configuration, run without -D:
The log should contain:
An explicit <greeting> value in the POM is the configured value for this project.
Use -Dsayhi.greeting=... when the parameter is not set literally in the POM, as in Step 6.
A literal <greeting> here is not overridden by that -D property.
The complete files¶
Since you edited pom.xml in Step 2 and GreetingMojo.java in Steps 3 and 6, here are the full files for the required path so you can compare against your own.
The optional pluginGroups settings from Step 7 and the optional <plugins> configuration from Step 8 are omitted.
pom.xml¶
<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>sample.plugin</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>Hello Maven Plugin</name>
<prerequisites>
<maven>3.9.0</maven>
</prerequisites>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.version>3.9.9</maven.version>
<maven.plugin.tools.version>3.15.2</maven.plugin.tools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${maven.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${maven.plugin.tools.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>${maven.plugin.tools.version}</version>
<executions>
<execution>
<id>help-mojo</id>
<goals>
<goal>helpmojo</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
src/main/java/sample/plugin/GreetingMojo.java¶
package sample.plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
/**
* Says "Hi" to the user.
*/
@Mojo(name = "sayhi")
public class GreetingMojo extends AbstractMojo {
/**
* The greeting to display.
*/
@Parameter(property = "sayhi.greeting", defaultValue = "Hello, world.")
private String greeting;
@Override
public void execute() throws MojoExecutionException {
getLog().info(greeting);
}
}
What you learned¶
You created a Maven 3 plugin project, implemented a Mojo with @Mojo and @Parameter, installed it locally, and ran the sayhi goal from the command line.
Next steps¶
-
Test a development version of a plugin
Run automated tests against the plugin before you release it.
-
Document a plugin to the standard
Generate user-facing docs from the annotations you just added.
-
Handle plugin prefix resolution
Control how short names like
hello:sayhimap to artifacts. -
Plugin architecture & the Mojo model
Deepen the mental model behind goals, descriptors, and classloading.
-
Look up annotations and APIs as you add more goals.