apache/maven · error · PluginExecutionException

A type incompatibility occurred while executing ${mojoDescri

Error message

A type incompatibility occurred while executing ${mojoDescriptor.getId()}: ${e.getMessage()}

What it means

A ClassCastException escaped a mojo execution. In Maven this almost always means class identity broke across classloaders: the same class name is loaded both from the plugin realm and from Maven core (or another realm), so a JVM-level cast between 'identical' types fails. Maven dumps the plugin realm and fails the build with PluginExecutionException.

Source

Thrown at impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java:195

        } catch (LinkageError e) {
            mojoExecutionListener.afterExecutionFailure(
                    new MojoExecutionEvent(session, project, mojoExecution, mojo, e));
            ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
            PrintStream ps = new PrintStream(os);
            ps.println("An API incompatibility was encountered while executing " + mojoDescriptor.getId() + ": "
                    + e.getClass().getName() + ": " + e.getMessage());
            pluginRealm.display(ps);
            Exception wrapper = new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), e);
            throw new PluginExecutionException(mojoExecution, project, wrapper);
        } catch (ClassCastException e) {
            mojoExecutionListener.afterExecutionFailure(
                    new MojoExecutionEvent(session, project, mojoExecution, mojo, e));
            ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
            PrintStream ps = new PrintStream(os);
            ps.println("A type incompatibility occurred while executing " + mojoDescriptor.getId() + ": "
                    + e.getMessage());
            pluginRealm.display(ps);
            throw new PluginExecutionException(mojoExecution, project, os.toString(), e);
        } catch (RuntimeException e) {
            mojoExecutionListener.afterExecutionFailure(
                    new MojoExecutionEvent(session, project, mojoExecution, mojo, e));
            throw e;
        } finally {
            mavenPluginManager.releaseMojo(mojo, mojoExecution);
            scope.exit();
            Thread.currentThread().setContextClassLoader(oldClassLoader);
            legacySupport.setSession(oldSession);
        }
    }

    /**
     * TODO pluginDescriptor classRealm and artifacts are set as a side effect of this
     *      call, which is not nice.
     * @throws PluginResolutionException
     */
    @Override

View on GitHub (pinned to e4093d4e12)

Solutions

  1. Read the cast message: 'class X cannot be cast to class X' with two different ClassLoaders named in the detail confirms a realm conflict.
  2. Find the duplicate artifact in the realm dump and exclude it from the plugin, or align its version with the one Maven core provides.
  3. If you own the plugin, depend on the shared API with provided scope instead of bundling it.
  4. Upgrade to a plugin version tested against your Maven release.

Example fix

<!-- before: plugin drags its own commons-cli 1.3, clashing with another copy in the realm -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <dependencies>
    <dependency>
      <groupId>commons-cli</groupId>
      <artifactId>commons-cli</artifactId>
      <version>1.3</version>
    </dependency>
  </dependencies>
</plugin>
<!-- after: drop the redundant dependency so a single copy loads -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
</plugin>
Defensive patterns

Strategy: type-guard

Type guard

static boolean realmCompatible(Object value, Class<?> target) {
    return value == null
        || (target.getClassLoader() == value.getClass().getClassLoader() && target.isInstance(value));
}

Try / catch

catch (ClassCastException e) {
    if (e.getMessage() != null && e.getMessage().contains("cannot be cast to")) {
        // the detail line names both ClassLoaders: a cross-realm leak, fix dependencies
        log.error("classloader clash: {}", e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: A plugin depends on a library that Maven core also exports at a different version, and an object created in one classloader is passed to code that casts it in the other. Also: two plugin dependencies ship the same classes in different artifacts and class resolution picks the incompatible copy.

Common situations: Plugins bundling guava/commons-*/plexus-utils versions that clash with Maven's own; shaded or relocated jars duplicating class names; extensions and plugins sharing types across realms.

Related errors


AI-assisted analysis of apache/maven@e4093d4e12 (2026-08-21). Data as JSON: /api/errors/85aa2689b9c4bd60. Report an issue: GitHub.