How Maven Plugin Version Resolution and Prefix Resolution Work: A Deep Dive into the Apache Maven Source Code
Maven resolves plugin prefixes by scanning the current POM for matching goal prefixes before falling back to repository metadata, and it resolves plugin versions by checking explicit POM declarations or querying remote repositories to select the latest compatible release.
When you run a command like mvn compiler:compile, the Apache Maven build system must map the short prefix to fully-qualified coordinates and determine which version to execute. The apache/maven source code implements this through two dedicated resolver components—DefaultPluginPrefixResolver and DefaultPluginVersionResolver—that handle prefix mapping and version selection in distinct but coordinated phases. Understanding Maven plugin version resolution and prefix resolution is essential for debugging build failures and optimizing repository performance.
How Maven Resolves Plugin Prefixes
The prefix resolution process translates a short goal prefix such as compiler into a complete groupId:artifactId. This logic lives in impl/maven-core/src/main/java/org/apache/maven/plugin/prefix/internal/DefaultPluginPrefixResolver.java.
Scanning Project-Declared Plugins
Maven first inspects the current project's POM, checking the <plugins> and <pluginManagement> sections for candidates. In DefaultPluginPrefixResolver.resolveFromProject (lines 46–60), the resolver filters plugins whose artifactId contains the requested prefix. For each candidate, it loads the plugin descriptor via BuildPluginManager.loadPlugin. If the descriptor's goalPrefix matches the request, Maven returns the result wrapped in DefaultPluginPrefixResult. This project-local check ensures that explicitly declared plugins take precedence over remote metadata.
Falling Back to Repository Metadata
If the prefix is not found in the POM, the resolver queries the repositories declared in the request. In DefaultPluginPrefixResolver.resolveFromRepository and processResults (lines 88–124 and 130–166), Maven iterates over candidate groupId values from the POM's <pluginGroups> or user-defined groups. For each group, it requests the maven-metadata.xml file via DefaultMetadata and reads the content with MetadataReader. The metadata yields <plugin> entries containing both artifactId and prefix. The first entry whose prefix equals the requested value and whose artifactId is permitted by the candidate group set is returned as DefaultPluginPrefixResult. If no match is found anywhere, Maven throws a NoPluginFoundForPrefixException.
How Maven Resolves Plugin Versions
Once the plugin coordinates are known, Maven determines the exact version to execute. This stage is governed by impl/maven-core/src/main/java/org/apache/maven/plugin/version/internal/DefaultPluginVersionResolver.java.
Project-Defined Versions
The resolver first checks the POM's <plugins> and <pluginManagement> sections for an explicit <version> element. In DefaultPluginVersionResolver.resolveFromProject (lines 73–78), if a version is declared directly in the project model, that value is returned immediately and no remote lookup occurs. This is the fastest and most deterministic path through Maven plugin version resolution.
Repository Metadata and Compatibility Selection
When the POM omits a version, Maven queries remote repositories for the plugin's maven-metadata.xml. It creates a DefaultMetadata request for the plugin's groupId/artifactId pair and dispatches it to all configured repositories. The returned metadata is parsed via MetadataReader.read and merged into a unified Versions structure in mergeMetadata, tracking the latest, release, and all available versions alongside their timestamps and source repositories.
The selection logic in DefaultPluginVersionResolver.selectVersion (lines 75–112) follows a strict priority:
- Prefer the release version (
releaseVersion). - If the release is incompatible, fall back to the latest version.
- If neither works, iterate over all versions—releases first, then snapshots—in descending order, picking the first candidate that passes the compatibility test.
The compatibility check, isCompatible, loads the plugin descriptor using MavenPluginManager.getPluginDescriptor and validates prerequisites through pluginManager.checkPrerequisites. If a version fails this validation, it is discarded and the next candidate is evaluated. The version resolver also caches successful lookups in a per-session ConcurrentMap keyed by groupId, artifactId, and repository list to avoid redundant remote fetches.
The Complete Resolution Flow
When you invoke mvn formatter:format, Maven executes both resolvers in sequence before running the goal:
- Prefix resolution —
DefaultPluginPrefixResolvermapsformatterto a fully-qualified plugin such asorg.apache.maven.plugins:maven-formatter-plugin. - Version resolution —
DefaultPluginVersionResolverchecks the POM for an explicit version. If none exists, it queries repository metadata, prefers the latest compatible release, and selects a suitable version.
If you specify the full coordinate including the version, such as mvn org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile, Maven bypasses both the prefix and version resolution lookups entirely.
Practical Examples
The following pom.xml snippet pins the compiler plugin version, allowing the project-defined resolution path to succeed immediately:
<project>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
To trigger prefix resolution from the command line, use the short form:
mvn compiler:compile
To bypass both prefix and version resolution, provide the fully-qualified coordinate with an explicit version:
mvn org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile
Summary
- Maven plugin prefix resolution first scans the current POM's
<plugins>and<pluginManagement>sections viaDefaultPluginPrefixResolver.resolveFromProject, then falls back to repositorymaven-metadata.xmlthroughresolveFromRepository. - If no prefix match is found in either location, Maven throws a
NoPluginFoundForPrefixException. - Maven plugin version resolution returns an explicitly declared POM version immediately via
DefaultPluginVersionResolver.resolveFromProject. - For undeclared versions, Maven queries remote metadata, merges results into a
Versionsobject, and selects the newest compatible release inDefaultPluginVersionResolver.selectVersion. - The
isCompatiblecheck validates each candidate against plugin prerequisites usingMavenPluginManager.getPluginDescriptor. - Successful version lookups are cached in a per-session
ConcurrentMapto eliminate repeated remote metadata fetches.
Frequently Asked Questions
What happens when Maven cannot resolve a plugin prefix?
Maven throws a NoPluginFoundForPrefixException after exhausting both the project POM scan in DefaultPluginPrefixResolver.resolveFromProject and the repository metadata search in resolveFromRepository. This indicates that the prefix either does not exist in the configured plugin groups or is not declared in the current project.
How does Maven choose between release and latest versions?
During repository-based version resolution, DefaultPluginVersionResolver.selectVersion first attempts to use the releaseVersion advertised in maven-metadata.xml. If isCompatible determines that the release is incompatible with the current Maven runtime, Maven falls back to the latest version, then iterates through remaining versions in descending order until it finds a compatible candidate.
Can I skip version resolution by specifying the full plugin coordinate?
Yes. When you provide a fully-qualified command such as mvn org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile, Maven uses the supplied groupId:artifactId:version directly. This bypasses both DefaultPluginPrefixResolver and the repository lookup logic inside DefaultPluginVersionResolver.
Where does Maven cache plugin version resolution results?
The default version resolver stores successful results in a per-session ConcurrentMap keyed by the plugin's groupId, artifactId, and the list of configured repositories. This cache prevents redundant remote metadata requests when the same plugin is referenced multiple times during a single build session.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →