Skip to content

Avoid duplicate dependencies in ChangeDependency - #8129

Merged
timtebeek merged 5 commits into
openrewrite:mainfrom
timtebeek:change-dependency-avoid-duplicates
Jul 10, 2026
Merged

Avoid duplicate dependencies in ChangeDependency#8129
timtebeek merged 5 commits into
openrewrite:mainfrom
timtebeek:change-dependency-avoid-duplicates

Conversation

@timtebeek

@timtebeek timtebeek commented Jun 26, 2026

Copy link
Copy Markdown
Member

What's changed

ChangeDependencyGroupIdAndArtifactId (the Maven engine behind org.openrewrite.java.dependencies.ChangeDependency) could produce a duplicate dependency: when the new coordinates were already declared directly at a lower version than the requested one, the recipe renamed the old declaration in place, leaving two declarations of the same dependency.

The recipe already removed the old declaration when the existing copy was at a version >= the requested one, but the version guard meant the lower-version case fell through to a rename and produced a duplicate.

This change:

  • Removes the old declaration whenever an existing direct dependency already declares the new coordinates, matched on full identity (groupId + artifactId + classifier + type + scope) — independent of version.
  • Upgrades the surviving declaration in place to the requested version pattern, reusing the recipe's existing version-resolution and managed-dependency handling.
  • Leaves declarations that differ on classifier, type, or scope untouched, since those are distinct dependencies.

Why

Concretely, migrating a POM that declares both jakarta.jws:jakarta.jws-api:3.0.0 and jakarta.xml.ws:jakarta.xml.ws-api:3.0.1 with ChangeDependency (jws-api → xml.ws-api, version 4.0.x) previously yielded two jakarta.xml.ws-api entries. It now drops jakarta.jws-api and upgrades the existing jakarta.xml.ws-api to 4.0.x — a single, correct entry.

Tests

  • Reworked shouldAddNewIfDependencyAlreadyExistsInOlderVersion (which asserted the duplicate) into shouldUpgradeExistingInPlaceIfDependencyAlreadyExistsInOlderVersion, asserting a single upgraded entry.
  • Added shouldNotLeaveDuplicateWhenExistingNewDependencyIsAtLowerVersion covering the jws/ws scenario.
  • Added shouldNotDeduplicateWhenClassifierDiffers confirming distinct coordinates are preserved.
  • Verified no regressions in ChangeManagedDependencyGroupIdAndArtifactIdTest and UpgradeDependencyVersionTest.

The Gradle ChangeDependency path is out of scope here, matching the Maven-only scope of the downstream compensation.

When the new coordinates were already declared directly and the existing
declaration was at a lower version than the requested one, the recipe renamed
the old dependency in place, producing two declarations of the same dependency.

Now the old declaration is removed whenever an existing direct dependency
already matches the new coordinates on groupId, artifactId, classifier, type,
and scope, and the surviving declaration is upgraded in place to the requested
version. Declarations that differ on classifier, type, or scope are left
untouched as distinct dependencies.
timtebeek added 2 commits July 9, 2026 19:28
…ching

Address review feedback:
- Dedupe now applies when only newGroupId or only newArtifactId changes,
  not just when both are provided.
- Match duplicates on full renamed coordinates (groupId/artifactId plus
  classifier/type/scope) rather than assuming exact pre-filtering, and
  rename the helpers to make the intent explicit.
- Remove explanatory comments now covered by method/variable names
- Collapse the dead first conjunct in dedupeEnabled
- Delete shadowing effectiveNew* locals in the exclusion pass
- Centralize the rename rule in renamedGroupId/renamedArtifactId helpers
- Rely on ResolvedDependency.getType() and the null-safe Scope.fromName
  instead of hand-rolled defaulting
@timtebeek
timtebeek marked this pull request as ready for review July 10, 2026 16:21

@steve-aom-elliott steve-aom-elliott 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 think it ended up correct, it's still just denser logic than I think ideally we'd be able to describe it as. Not sure how to cleanly simplify further, though.

@github-project-automation github-project-automation Bot moved this from In Progress to Ready to Review in OpenRewrite Jul 10, 2026
@timtebeek
timtebeek merged commit f2383c9 into openrewrite:main Jul 10, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from Ready to Review to Done in OpenRewrite Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants