Skip to content

Instantly share code, notes, and snippets.

@Shawyeok
Created March 24, 2025 09:24
Show Gist options
  • Save Shawyeok/f41995b69839adb46cf5c231f04a9e1c to your computer and use it in GitHub Desktop.
Save Shawyeok/f41995b69839adb46cf5c231f04a9e1c to your computer and use it in GitHub Desktop.
JarConflictFinder: A Java tool to identify duplicate classes across multiple JAR files in your project. This tool helps detect potential classpath conflicts by scanning directories or individual JAR files and reporting classes that appear in multiple JARs.
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
public class JarConflictFinder {
private Map<String, List<String>> classLocations = new HashMap<>();
public void scanDirectory(String directoryPath) {
File directory = new File(directoryPath);
if (!directory.isDirectory()) {
System.err.println("Not a valid directory: " + directoryPath);
return;
}
File[] jarFiles = directory.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
if (jarFiles != null) {
for (File jarFile : jarFiles) {
scanJarFile(jarFile.getAbsolutePath());
}
}
}
public void scanJarFile(String jarPath) {
try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class")) {
// Convert path to class name
String className = entry.getName()
.replace('/', '.')
.replace(".class", "");
classLocations.computeIfAbsent(className, k -> new ArrayList<>())
.add(jarFile.getName());
}
}
} catch (IOException e) {
System.err.println("Error processing JAR file: " + jarPath);
e.printStackTrace();
}
}
public void printDuplicates() {
System.out.println("Found duplicate classes:");
System.out.println("------------------------");
boolean foundDuplicates = false;
for (Map.Entry<String, List<String>> entry : classLocations.entrySet()) {
if (entry.getValue().size() > 1) {
foundDuplicates = true;
System.out.println("\nClass: " + entry.getKey());
System.out.println("Found in:");
for (String jarPath : entry.getValue()) {
System.out.println(" - " + jarPath);
}
}
}
if (!foundDuplicates) {
System.out.println("No duplicate classes found.");
}
// Print statistics after showing duplicates
printStatistics();
}
private void printStatistics() {
System.out.println("\nDuplicate Class Statistics:");
System.out.println("==========================");
// 1. Total number of duplicate classes
long totalDuplicates = classLocations.values().stream()
.filter(list -> list.size() > 1)
.count();
System.out.println("Total duplicate classes found: " + totalDuplicates);
// 2. Conflicts per JAR pair
Map<String, Integer> jarPairConflicts = new HashMap<>();
for (List<String> jars : classLocations.values()) {
if (jars.size() > 1) {
for (int i = 0; i < jars.size(); i++) {
for (int j = i + 1; j < jars.size(); j++) {
String pair = jars.get(i) + " <-> " + jars.get(j);
jarPairConflicts.merge(pair, 1, Integer::sum);
}
}
}
}
System.out.println("\nConflicts per JAR pair:");
jarPairConflicts.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.limit(10) // Show top 10 conflicting pairs
.forEach(e -> System.out.println(String.format(" %s: %d conflicts", e.getKey(), e.getValue())));
// 3. Distribution of duplicate counts
Map<Integer, Long> duplicateDistribution = classLocations.values().stream()
.filter(list -> list.size() > 1)
.collect(Collectors.groupingBy(
List::size,
Collectors.counting()
));
System.out.println("\nDistribution of duplicates:");
duplicateDistribution.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> System.out.println(String.format(" Classes found in %d JARs: %d", e.getKey(), e.getValue())));
}
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Usage: java JarConflictFinder <directory-or-jar-path> [additional-jar-paths...]");
return;
}
JarConflictFinder finder = new JarConflictFinder();
for (String path : args) {
File file = new File(path);
if (file.isDirectory()) {
finder.scanDirectory(path);
} else if (path.toLowerCase().endsWith(".jar")) {
finder.scanJarFile(path);
} else {
System.err.println("Skipping invalid path: " + path);
}
}
finder.printDuplicates();
}
}
@Shawyeok
Copy link
Author

Shawyeok commented Mar 24, 2025

JarConflictFinder

A Java tool to identify duplicate classes across multiple JAR files in your project. This tool helps detect potential classpath conflicts by scanning directories or individual JAR files and reporting classes that appear in multiple JARs.

Features

  • Scan entire directories for JAR files
  • Scan individual JAR files
  • Identify duplicate classes across JARs
  • Generate detailed statistics about duplicates
  • Show conflict patterns between JAR pairs
  • Display distribution of duplicate classes

Prerequisites

  • Java 8 or higher
  • Java Development Kit (JDK) for compilation

Installation

  1. Download JarConflictFinder.java
  2. Compile the Java file:
javac JarConflictFinder.java

Usage

The tool can be used in several ways:

1. Scan a Directory

To scan all JAR files in a directory:

java JarConflictFinder /path/to/directory

2. Scan Specific JAR Files

To scan specific JAR files:

java JarConflictFinder file1.jar file2.jar file3.jar

3. Mixed Scanning

You can combine directory and file scanning:

java JarConflictFinder /path/to/directory file1.jar /another/directory

Output Format

The tool provides output in three sections:

  1. Duplicate Classes List

    Class: com.example.MyClass
    Found in:
      - first.jar
      - second.jar
    
  2. Conflict Statistics

    • Total number of duplicate classes
    • Top conflicting JAR pairs
    • Distribution of duplicates across JARs
  3. Distribution Analysis

    • Shows how many classes are found in 2, 3, or more JARs

Example Output

Found duplicate classes:
------------------------

Class: org.example.util.Helper
Found in:
  - lib1.jar
  - lib2.jar

Duplicate Class Statistics:
==========================
Total duplicate classes found: 42

Conflicts per JAR pair:
  lib1.jar <-> lib2.jar: 15 conflicts
  lib2.jar <-> lib3.jar: 12 conflicts

Distribution of duplicates:
  Classes found in 2 JARs: 38
  Classes found in 3 JARs: 4

Best Practices

  1. Run this tool during your build process to detect potential conflicts early
  2. Use it before deploying to production to ensure no conflicting dependencies
  3. Check for conflicts after adding new dependencies
  4. Pay special attention to different versions of the same library

Troubleshooting

If you encounter any issues:

  1. Ensure you have proper read permissions for the directories and JAR files
  2. Verify that the paths provided are correct
  3. Check if the JAR files are valid and not corrupted
  4. For large directories, ensure sufficient memory is available to the JVM

License

This tool is provided as-is under the MIT License.

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