Skip to content

MNG-8099: Add explicit 'api' scope for dependencies and make 'compile' non-transitive for Maven 4 - #12745

Open
Hiteshsai007 wants to merge 1 commit into
apache:masterfrom
Hiteshsai007:mng-8099-api-scope
Open

MNG-8099: Add explicit 'api' scope for dependencies and make 'compile' non-transitive for Maven 4#12745
Hiteshsai007 wants to merge 1 commit into
apache:masterfrom
Hiteshsai007:mng-8099-api-scope

Conversation

@Hiteshsai007

@Hiteshsai007 Hiteshsai007 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Resolves #10786 (MNG-8099)

Summary

This PR introduces a new transitive api dependency scope for Maven 4 and makes the existing compile scope non-transitive. This aligns Maven's dependency model with modern build tools like Gradle, which distinguish between API (publicly exposed) and implementation (internal) dependencies.

Problem

Currently in Maven, the compile scope is transitive — meaning if library A depends on library B with compile scope, any project depending on A will also see B on its compile classpath. This leads to "leaky" dependency graphs where implementation details are exposed to consumers, causing:

  • Unnecessary coupling between modules
  • Fragile builds that break when internal dependencies change
  • Bloated classpaths with dependencies that consumers don't actually need

Solution

1. New api Scope (DependencyScope.java)

  • Added API("api", true) — a transitive scope for dependencies that form part of the public API
  • Changed COMPILE("compile", false) from transitive to non-transitive — for implementation-only dependencies

2. Scope Manager Configuration (Maven4ScopeManagerConfiguration.java)

  • Registered the api dependency scope in both:
    • impl/maven-impl/.../Maven4ScopeManagerConfiguration.java
    • compat/maven-resolver-provider/.../Maven4ScopeManagerConfiguration.java
  • The api scope is configured with all() build paths (compile + runtime), matching compile's path visibility

3. Path Scope Updates (PathScope.java)

  • Added DependencyScope.API to all four standard path scopes:
    • MAIN_COMPILE — api dependencies appear on the compile classpath
    • MAIN_RUNTIME — api dependencies appear on the runtime classpath
    • TEST_COMPILE — api dependencies appear on the test compile classpath
    • TEST_RUNTIME — api dependencies appear on the test runtime classpath

4. Model Validation (DefaultModelValidator.java)

  • Added DependencyScope.API to the list of Maven 4-only scopes that are rejected when used with modelVersion 4.0.0 (legacy POMs)
  • This ensures backward compatibility: the api scope is only valid for Maven 4.1.0+ model versions

Usage (Maven 4)

<!-- Public API dependency — transitive to consumers -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>api-lib</artifactId>
    <scope>api</scope>
</dependency>

<!-- Implementation dependency — NOT transitive to consumers -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>impl-lib</artifactId>
    <scope>compile</scope>
</dependency>

Backward Compatibility

  • Maven 3 / modelVersion 4.0.0: No change. The compile scope continues to behave as before (transitive) through Maven3ScopeManagerConfiguration, and the api scope is rejected by validation.
  • Maven 4 / modelVersion 4.1.0+: The new behavior applies. Projects must explicitly use api for dependencies they want to expose transitively.

Following this checklist to help us incorporate your contribution quickly and easily:

Checklist

  • Your pull request should address just one issue, without pulling in other changes.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Each commit in the pull request should have a meaningful subject line and body.
  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied.
  • Run mvn verify to make sure basic checks pass.
  • You have run the Core IT successfully.

To make clear that you license your contribution under the Apache License Version 2.0, January 2004, you have to acknowledge this by using the following checkbox.

  • I hereby declare this contribution to be licenced under the Apache License Version 2.0, January 2004
  • In any other case, please file an Apache Individual Contributor License Agreement.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on MNG-8099, @Hiteshsai007! The api/compile scope split concept is well-motivated (mirroring Gradle's api/implementation distinction). A few issues need to be addressed:


🔴 Critical: Consumer POM regression — compile-scoped dependencies silently stripped

DefaultConsumerPomBuilder.hasDependencyScope() uses !scope.isTransitive() to decide which dependencies to remove from consumer POMs. With COMPILE changing from transitive=true to transitive=false, all compile-scoped dependencies — and dependencies with no explicit scope (the most common case, which defaults to COMPILE) — will be stripped from consumer POMs.

This breaks downstream dependency resolution for essentially every Maven 4 project. The method is not gated on model version, so even modelVersion=4.0.0 projects are affected. The PR's backward compatibility claim ("Maven 3 / modelVersion 4.0.0: No change") is incorrect for this code path.

This is the same regression identified in our review of PR #12723. The fix requires hasDependencyScope() to use a different criterion than isTransitive() — e.g., checking whether the scope should appear in consumer POMs (compile, api, runtime) directly.


🔴 Critical: Resolver treats compile as non-transitive

Both Maven4ScopeManagerConfiguration files pass DependencyScope.COMPILE.isTransitive() to createDependencyScope(). After this change, the resolver will treat compile as non-transitive in Maven 4, meaning transitive dependencies of compile-scoped libraries won't be resolved — a massive behavioral change with no migration path.


🔴 Accidental files committed

Two files are included in the diff that shouldn't be:

  • issue_comment.md — a binary (UTF-16) file containing a GitHub issue comment about PR #12744
  • plexus-sec-dispatcher — a git submodule reference (160000 mode) pointing to commit a3b5741

Both must be removed before merging.


🔴 No tests provided

The PR checklist marks "Write unit tests" as complete, but zero test files are modified or added. A change of this magnitude to Maven's dependency scope system needs comprehensive test coverage for:

  • Consumer POM generation with compile vs api-scoped dependencies
  • Transitive resolution behavior for both scopes
  • Model validation of api scope in 4.0.0 vs 4.1.0 POMs
  • Backward compatibility with Maven 3

🟡 MavenModelVersion does not detect api scope

The auto-generated MavenModelVersion class does not check for api-scoped dependencies. Since API.isTransitive()=true, api-scoped deps survive hasDependencyScope() filtering, but the consumer POM could be written with modelVersion=4.0.0 — creating an inconsistency where a 4.0.0 POM contains a scope only valid in 4.1.0+.


Recommendations:

  1. Update hasDependencyScope() to not rely on isTransitive() for determining consumer POM inclusion
  2. Gate the compile→non-transitive behavior on model version (as MNG-8099 description states: "only with the new modelVersion to opt into")
  3. Remove the accidental files
  4. Add comprehensive tests
  5. Address MavenModelVersion detection of the api scope

The direction is right — the implementation just needs more work to handle the cross-cutting impacts. Happy to re-review once updated!

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of gnodet

@desruisseaux

Copy link
Copy Markdown
Contributor

I question the idea that only API dependencies should be transitive and not the dependencies with the compile scope. Even if a project does not expose a dependency in its API, if that project needs that dependency for its working, then the dependency must be on the classpath.

Or maybe you mean compiler as "transitive for Surefire but not transitive for the compiler"? Do we need to add this complexity when Java module already handle that for us? I would rather suggest to keep Maven as it stands today, where compiler scope means "put the dependency on the module-path", then module-info tells whether that dependency shall be visible for users of that project or hidden as an implementation details.

In other words, keep a separation of tasks: Maven controls what to put on the module-path, and module-info controls which ones of these dependencies are API. The two are complementary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MNG-8099] Add explicit "api" scope for dependencies

3 participants