Skip to content

[MNG-8749] Remove PathTranslator and UrlNormalizer from public API - #12941

Merged
elharo merged 3 commits into
masterfrom
MNG-8749-hide-path-translator-from-api
Sep 2, 2026
Merged

[MNG-8749] Remove PathTranslator and UrlNormalizer from public API#12941
elharo merged 3 commits into
masterfrom
MNG-8749-hide-path-translator-from-api

Conversation

@elharo

@elharo elharo commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #10326 / MNG-8749.

Summary

PathTranslator and UrlNormalizer in org.apache.maven.api.services.model are pure stateless functions with exactly one implementation each, no external reimplementations, and no reason to be injectable. This PR removes both interfaces from the public API and converts their implementations to static utility methods.

Changes

Interfaces removed

  • api/maven-api-spi/.../services/model/PathTranslator.java — deleted
  • api/maven-api-spi/.../services/model/UrlNormalizer.java — deleted

Implementations converted to static utility classes

  • DefaultPathTranslatorfinal class with private constructor; alignToBaseDirectory(String, Path) is now a static method
  • DefaultUrlNormalizerfinal class with private constructor; normalize(String) is now a static method

Both classes had their @Named, @Singleton, and @Inject annotations removed since they no longer participate in DI.

Internal consumers updated (6 classes)

  • DefaultModelInterpolator — constructor reduced from 4 params to 2 (RootLocator, Interpolator); uses static calls
  • DefaultModelPathTranslator — removed PathTranslator field and constructor param; uses static call
  • DefaultModelUrlNormalizer — removed UrlNormalizer field and constructor param; uses static call
  • DefaultModelBuilder — removed PathTranslator field and constructor param
  • DefaultProfileActivationContext — removed PathTranslator from all constructors; uses static call
  • maven-testing stubs (MojoExtension, RepositorySystemSupplier) — simplified constructor wiring

Rationale

Analysis of the 22 interfaces in org.apache.maven.api.services.model shows they fall into three tiers:

Tier Interfaces Reason to keep
Tier 1 — Actively reimplemented ModelProcessor, ProfileActivator, ProfileSelector, ModelValidator, ModelInterpolator Extensions override these in the wild
Tier 2 — Required by contracts ProfileActivationContext, RootDetector, ModelVersionParser, ModelPathTranslator, ModelUrlNormalizer, LifecycleBindingsInjector, PluginConfigurationExpander, ModelTransformer, ModelParser, ModelNormalizer, DependencyManagementImporter, DependencyManagementInjector, InheritanceAssembler, PluginManagementInjector, ProfileInjector Parameter types in extension APIs, ServiceLoader contracts, or composite orchestrators
Tier 3 — Safe to remove PathTranslator, UrlNormalizer Pure functions, zero external implementations, no extension point value

This PR removes Tier 3 — the two interfaces that provide no extension-point value and are better expressed as static methods.

@elharo
elharo marked this pull request as draft August 30, 2026 16:09

@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.

I don't think "single implementation" is a valid criterion for removing an SPI here. If it were, we'd need to remove nearly the entire services/model package — 14 out of 18 interfaces there have exactly one implementation:

DependencyManagementImporter, DependencyManagementInjector, InheritanceAssembler, ModelInterpolator, ModelNormalizer, ModelPathTranslator, ModelUrlNormalizer, ModelValidator, PluginConfigurationExpander, PluginManagementInjector, ProfileInjector, ProfileSelector, RootLocator, UrlNormalizer — all have a single impl.

That's not a design smell — that's the design. This package is an injection-based extension mechanism: each interface has one default implementation, and the interface exists so that extensions can swap in alternatives via DI. As I mentioned in the issue, plugins and extensions do customize model building by replacing individual components. Removing the interface and hardcoding DefaultPathTranslator in constructor signatures closes that extensibility point.

The cost of keeping a 1-method interface is effectively zero. The cost of removing it and needing to reintroduce it later is a breaking change for anyone who coded against the current shape.

@elharo

elharo commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

I think this approach is a large heap of YAGNI. This is just the one example I happened to stumble in first. Removing it from the public API makes the API and system simpler and still allows it to be added in later if it anyone ever needs, though I very much doubt anyone ever will.

@gnodet

gnodet commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

I did some research into what plugins and extensions in the Maven ecosystem actually use from the services/model package. Here's what I found — it might help frame the discussion beyond PathTranslator specifically.

The package splits into three tiers based on real-world external usage:

Tier 1 — Actively reimplemented by plugins (must keep as SPI)

These have multiple independent custom implementations across the ecosystem (Maven 3 API today, which is the evidence base for what Maven 4 will need):

  • ModelProcessor — polyglot-maven, jgitver, qoomon/maven-git-versioning-extension, maven-tiles, Spring Boot thin launcher. The most-replaced interface in the entire model-building SPI.
  • ProfileActivator — random-maven/profile-activator-extension (MVEL), kpiwko/el-profile-activator, stephenc/docker-maven-profile-activator, rrialq/jsr223-profile-activator.
  • ProfileSelector — random-maven (AND-linked activation), johnjcool/and-activation-profile-selector, sviperll/ozymandias.
  • ModelValidator — JetBrains IntelliJ, XMvn/Fedora, Spring Gradle dependency-management-plugin.
  • ModelInterpolator — flatten-maven-plugin, Spring Gradle, JetBrains IntelliJ.

Tier 2 — Required by Tier 1 contracts or deliberately extensible

Not reimplemented themselves, but needed by the interfaces that are:

  • ProfileActivationContext — parameter type in ProfileActivator.isActive() and ProfileSelector.getActiveProfiles(). Removing it from the SPI breaks the contract for all Tier 1 activator/selector extensions.
  • RootDetector — extends Service, loaded via ServiceLoader in DefaultRootLocator, already has 2 implementations (DotMvnRootDetector, PomXmlRootDetector). Explicitly designed for extension.
  • ModelVersionParser — intentionally decoupled from VersionParser (the javadoc says so explicitly) to let model building work without a full Maven session. Used as a constructor parameter in PropertyProfileActivator.
  • ModelResolver — already reimplemented against the Maven 4 API by mizdebsk/dola-gleaner.
  • ProfileInjector, RootLocator — consumed externally.

Tier 3 — No external usage, safe to remove

Zero reimplementations, not part of any extension contract:

  • PathTranslatoralignToBaseDirectory(String, Path)
  • UrlNormalizernormalize(String)

Both are pure stateless functions — no fields, no dependencies, no side effects. The entire Tier 3 SPI surface is two utility methods.

Rather than replacing interface-based DI with concrete-class-based DI (which keeps injecting a stateless singleton through 4+ constructors), a cleaner approach would be to make both static methods — eliminating the interface, the DI plumbing, and the constructor parameters from the 6 consumers altogether.

@elharo

elharo commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

That makes sense. Static methods are good for pure functions like these. Not every function needs to be turned into a pluggable object. Some should just work.

elharo and others added 3 commits September 1, 2026 23:08
org.apache.maven.api.services.model.PathTranslator has exactly one
implementation that is only used internally. Remove it from the public
API and fold its single alignToBaseDirectory method into the internal
DefaultPathTranslator implementation.

Closes #10326
Both interfaces are stateless pure functions with zero external
reimplementations. Instead of replacing interface-based DI with
concrete-class DI (the previous commit's approach), convert both
to static utility methods — eliminating the interfaces, the DI
plumbing, and the constructor parameters from all consumers.

- Delete PathTranslator interface (already done)
- Delete UrlNormalizer interface
- Make DefaultPathTranslator.alignToBaseDirectory static
- Make DefaultUrlNormalizer.normalize static
- Remove constructor parameters from DefaultModelBuilder,
  DefaultModelInterpolator, DefaultModelPathTranslator,
  DefaultModelUrlNormalizer, and DefaultProfileActivationContext

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gnodet
gnodet force-pushed the MNG-8749-hide-path-translator-from-api branch from 4f52772 to 010595d Compare September 1, 2026 21:12
@gnodet gnodet changed the title [MNG-8749] Hide PathTranslator from API [MNG-8749] Remove PathTranslator and UrlNormalizer from public API Sep 1, 2026
@gnodet gnodet added this to the 4.0.0-rc-7 milestone Sep 1, 2026
@gnodet
gnodet marked this pull request as ready for review September 2, 2026 06:21

@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.

Clean removal of two pure-function SPI interfaces (PathTranslator, UrlNormalizer) from the public API, converting them to static methods on final utility classes. Well-motivated — both are stateless functions with no fields, no dependencies, no side effects, and zero external reimplementations (confirmed by gnodet's Tier 1/2/3 analysis). Eliminates unnecessary DI boilerplate across 6+ constructor chains.

Observations (informational):

  • The compat layer is unaffected — org.apache.maven.project.path.PathTranslator and org.apache.maven.model.path.UrlNormalizer are separate deprecated interfaces in different packages.
  • Three incidental changes from the rebase commit (unused IOException import removal, blank line cleanup, test throws clause) are harmless but ideally would be in a separate commit for cleaner history.
  • Since 4.0.0 is still in RC, this API removal is appropriate timing. The priority:blocker on the JIRA issue aligns with the rc-7 milestone target.

📋 PR Metadata

Aspect Current Suggested
Labels (none) removed

🤖 This review was generated by ForgeBot.

@elharo
elharo merged commit 589c1eb into master Sep 2, 2026
23 checks passed
@elharo
elharo deleted the MNG-8749-hide-path-translator-from-api branch September 2, 2026 11:24
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

@elharo Please assign appropriate label to PR according to the type of change.

@elharo elharo added the java Pull requests that update Java code label Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

java Pull requests that update Java code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MNG-8749] Hide PathTranslator from API

3 participants