Skip to content

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:

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:

mkdir -p hello-maven-plugin/src/main/java/sample/plugin \
  && touch hello-maven-plugin/pom.xml
$root = "hello-maven-plugin"
New-Item -ItemType Directory -Path "$root/src/main/java/sample/plugin" -Force | Out-Null
New-Item -ItemType File -Path "$root/pom.xml" -Force | Out-Null

Verify: list the project tree for your platform. It should match the layout below.

ls -R hello-maven-plugin

Or tree hello-maven-plugin if you have it.

Get-ChildItem -Recurse hello-maven-plugin

Or tree /F hello-maven-plugin in Command Prompt.

hello-maven-plugin/
├── pom.xml
└── src
    └── main
        └── java
            └── sample
                └── plugin

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-plugin packaging applies the plugin build lifecycle, including plugin descriptor generation.
  • <prerequisites><maven> declares the minimum Maven version this plugin requires.
  • maven-plugin-api provides AbstractMojo and related Maven 3 APIs.
  • maven-plugin-annotations provides @Mojo and @Parameter.
  • provided scope keeps those APIs off the plugin JAR because Maven supplies them at runtime.
  • Pinning maven-plugin-plugin in pluginManagement sets which Plugin Tools version that lifecycle uses for descriptor generation.
  • The helpmojo execution generates a help goal 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:

mvn validate

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:

mvn compile

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:

mvn install

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:

mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi

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:

mvn sample.plugin:hello-maven-plugin:sayhi

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:

[INFO] Hello, Maven plugin developers!

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:

mvn hello:sayhi -Dsayhi.greeting="Hi from a short prefix!"

Verify: the log contains:

[INFO] Hi from a short prefix!

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:

mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi

The log should contain:

[INFO] Welcome

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