Skip to content

Conversation

@jkschneider
Copy link
Member

@jkschneider jkschneider commented Jan 26, 2026

Summary

  • Adds a new FindDuplicateClasses recipe that detects classes appearing in multiple dependencies on the classpath
  • Reports findings via a DuplicateClassesReport data table with project, source set, type name, and conflicting dependencies
  • Filters out module-info and package-info false positives from multi-release JARs

Problem

Duplicate classes on the classpath can cause runtime issues when different versions of the same class are loaded. The basepom duplicate-finder-maven-plugin helps detect this, but there was no OpenRewrite equivalent.

Solution

The recipe uses JavaSourceSet.gavToTypes to invert the GAV→types mapping and find types that appear in more than one dependency. The gavToTypes map is populated when the full classpath is scanned (e.g., via mod CLI or JavaSourceSet.build()), providing accurate dependency-to-type mappings.

Test plan

  • Existing tests pass

  • New tests added using real SLF4J binding duplicates (logback-classic vs slf4j-nop)

  • Tests verify no false positives with single-dependency classpath

  • Fixes moderneinc/customer-requests#878

Adds a new recipe that detects classes appearing in multiple dependencies
on the classpath, similar to basepom's duplicate-finder-maven-plugin.

The recipe uses JavaSourceSet.gavToTypes to invert the GAV→types mapping
and find types that appear in more than one dependency. Results are
reported via a DuplicateClassesReport data table with project, source set,
type name, and the dependencies containing the duplicate.

Filters out module-info and package-info false positives from multi-release
JARs.

Fixes moderneinc/customer-requests#878
import org.openrewrite.java.tree.JavaType;

import java.util.*;
import java.util.stream.Collectors;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.joining;


@Override
public Collection<? extends SourceFile> generate(Accumulator acc, ExecutionContext ctx) {
return Collections.emptyList();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return Collections.emptyList();
return emptyList();

JavaVisitor is more appropriate since Java source files are more likely
to have the JavaSourceSet marker needed for duplicate detection.
Comment on lines +42 to +52
@Override
public String getDisplayName() {
return "Find duplicate classes on the classpath";
}

@Override
public String getDescription() {
return "Detects classes that appear in multiple dependencies on the classpath. " +
"This is similar to what the Maven duplicate-finder-maven-plugin does. " +
"Duplicate classes can cause runtime issues when different versions " +
"of the same class are loaded.";
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
@Override
public String getDisplayName() {
return "Find duplicate classes on the classpath";
}
@Override
public String getDescription() {
return "Detects classes that appear in multiple dependencies on the classpath. " +
"This is similar to what the Maven duplicate-finder-maven-plugin does. " +
"Duplicate classes can cause runtime issues when different versions " +
"of the same class are loaded.";
String displayName = "Find duplicate classes on the classpath";
String description = "Detects classes that appear in multiple dependencies on the classpath. " +
"This is similar to what the Maven duplicate-finder-maven-plugin does. " +
"Duplicate classes can cause runtime issues when different versions " +
"of the same class are loaded.";

Comment on lines +113 to +115
String additionalDeps = gavs.size() > 2
? gavs.subList(2, gavs.size()).stream().collect(joining(", "))
: "";
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
String additionalDeps = gavs.size() > 2
? gavs.subList(2, gavs.size()).stream().collect(joining(", "))
: "";
String additionalDeps = gavs.size() > 2 ?
gavs.subList(2, gavs.size()).stream().collect(joining(", ")) :
"";

@jkschneider jkschneider merged commit e1aac53 into main Jan 26, 2026
2 checks passed
@jkschneider jkschneider deleted the jkschneider/find-duplicate-classes branch January 26, 2026 17:01
@github-project-automation github-project-automation bot moved this from In Progress to Done in OpenRewrite Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants