Building a Lightweight, Runtime‑Visible Jar Conflict Detector for Spring Boot

This article explains the pain points of Jar package conflicts in Spring Boot projects, outlines the limitations of existing tools, and presents a lightweight, embeddable solution that performs runtime scanning, three‑dimensional conflict detection, configurable rule‑based advice, and a web UI for visualizing results.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Building a Lightweight, Runtime‑Visible Jar Conflict Detector for Spring Boot

1. Pain Point Background

In Spring Boot development and operations, Jar package conflicts are a major headache. Common conflict scenarios include:

Class duplication : different dependencies bring the same class, causing ClassCastException or NoSuchMethodError.

Version conflict : mixing different versions of the same library leads to inconsistent behavior that only appears in production.

Log chaos : coexistence of SLF4J, Logback, and Log4j results in abnormal log output.

Driver duplication : both MySQL 5.x and 8.x drivers exist, causing connection errors.

Existing solutions have limitations: mvn dependency:tree only analyses at compile time, IDE plugins require manual operation, and third‑party tools are heavyweight and hard to embed.

We need a lightweight, embeddable, runtime‑visible Jar conflict detection tool.

2. Technical Solution Design

2.1 Core Architecture

运行时扫描 → 冲突检测 → 配置化建议 → Web 可视化
    ↓          ↓          ↓
ClassLoader   规则引擎   模板系统
适配器       智能分析   变量替换

2.2 Key Technical Points

1. Multi‑environment ClassLoader adaptation

public List<URL> getClasspathUrls() {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    List<URL> urls = new ArrayList<>();
    // Traverse all ClassLoader levels
    ClassLoader current = classLoader;
    while (current != null) {
        if (current instanceof URLClassLoader urlClassLoader) {
            urls.addAll(Arrays.asList(urlClassLoader.getURLs()));
        }
        current = current.getParent();
    }
    // Special handling for Spring Boot LaunchedURLClassLoader
    if (classLoader.getClass().getName().contains("LaunchedURLClassLoader")) {
        urls.addAll(extractFromLaunchedClassLoader(classLoader));
    }
    return urls.stream().distinct().toList();
}

2. Three‑dimensional conflict detection algorithm

// Class duplicate detection
Map<String, List<JarInfo>> classToJarsMap = new HashMap<>();
for (JarInfo jar : jars) {
    for (String className : jar.getClasses()) {
        classToJarsMap.computeIfAbsent(className, k -> new ArrayList<>()).add(jar);
    }
}
// Version conflict detection
Map<String, List<JarInfo>> nameToJarsMap = jars.stream()
    .collect(Collectors.groupingBy(JarInfo::getName));
// JAR duplicate detection (based on signature)
Map<String, List<JarInfo>> signatureMap = jars.stream()
    .collect(Collectors.groupingBy(this::generateJarSignature));

3. Configurable rule engine (YAML)

conflict:
  advisor:
    rules:
      slf4j-logging:
        patterns: [".*slf4j.*", ".*logback.*", ".*log4j.*"]
        severity: HIGH
        advice: |
          🚨 日志框架冲突!
          当前冲突:${className}
          涉及JAR:${jarList}
        解决方案:
          1. 排除多余的日志实现
          2. 统一使用 logback-classic
          3. 配置示例:
            <exclusion>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-simple</artifactId>
            </exclusion>

3. Core Implementation

3.1 Jar Scanner

Supports intelligent scanning in both development and production environments.

@Component
public class JarScanner {
    public List<JarInfo> scanJars() {
        List<JarInfo> jars = new ArrayList<>();
        List<URL> urls = classLoaderAdapter.getClasspathUrls();
        for (URL url : urls) {
            String path = url.getPath();
            if (shouldExclude(path)) continue;
            if (path.endsWith(".jar")) {
                jars.add(scanJarFile(url));
            } else if (path.contains("target/classes")) {
                jars.add(scanClassesDirectory(url));
            }
        }
        return jars;
    }
    private JarInfo scanJarFile(URL url) {
        try (JarFile jar = new JarFile(new File(url.toURI()))) {
            JarInfo jarInfo = new JarInfo();
            jarInfo.setName(extractJarName(jar.getName()));
            jarInfo.setVersion(extractVersion(jar));
            List<String> classes = jar.stream()
                .filter(entry -> entry.getName().endsWith(".class"))
                .map(entry -> entry.getName()
                    .replace("/", ".")
                    .replace(".class", ""))
                .toList();
            jarInfo.setClasses(classes);
            return jarInfo;
        } catch (Exception e) {
            logger.warn("Failed to scan jar: {}", url, e);
            return null;
        }
    }
}

3.2 Configurable Advice Generator

Zero hard‑coding, fully driven by configuration.

@Component
@ConfigurationProperties(prefix = "conflict.advisor")
public class ConflictAdvisor {
    private Map<String, RuleDefinition> rules = new HashMap<>();
    private List<SeverityRule> severityRules = new ArrayList<>();
    public void generateAdvice(List<ConflictInfo> conflicts) {
        for (ConflictInfo conflict : conflicts) {
            String identifier = extractIdentifier(conflict);
            for (RuleDefinition rule : rules.values()) {
                if (rule.matches(identifier)) {
                    conflict.setSeverity(rule.getSeverity());
                    conflict.setAdvice(formatAdvice(rule.getAdvice(), conflict));
                    break;
                }
            }
        }
    }
    private String formatAdvice(String template, ConflictInfo conflict) {
        Map<String, String> variables = buildVariables(conflict);
        String result = template;
        for (Map.Entry<String, String> entry : variables.entrySet()) {
            result = result.replace("${" + entry.getKey() + "}", entry.getValue());
        }
        return result;
    }
    private Map<String, String> buildVariables(ConflictInfo conflict) {
        Map<String, String> variables = new HashMap<>();
        variables.put("className", conflict.getClassName());
        variables.put("conflictType", getConflictTypeText(conflict.getType()));
        variables.put("jarCount", String.valueOf(conflict.getConflictingJars().size()));
        variables.put("jars", conflict.getConflictingJars().stream()
            .map(jar -> jar.getName() + ":" + jar.getVersion())
            .collect(Collectors.joining(", ")));
        variables.put("jarList", conflict.getConflictingJars().stream()
            .map(jar -> jar.getName() + ":" + jar.getVersion())
            .collect(Collectors.joining("
")));
        return variables;
    }
}

3.3 Frontend UI

<div class="bg-white rounded-lg shadow">
  <div class="p-6 border-b border-gray-200">
    <h3 class="text-lg font-medium text-gray-900">冲突详情</h3>
  </div>
  <div class="overflow-x-auto">
    <table class="min-w-full">
      <thead class="bg-gray-50">
        <tr>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">类名/Jar包名</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">严重程度</th>
          <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">修复建议</th>
        </tr>
      </thead>
      <tbody id="conflictsTableBody" class="bg-white divide-y divide-gray-200">
        <!-- 动态生成冲突数据 -->
      </tbody>
    </table>
  </div>
</div>

4. Configurable Rule System

4.1 Rule Definition Syntax

conflict:
  advisor:
    rules:
      database-driver:
        patterns:
          - ".*mysql.*"
          - ".*postgresql.*"
          - ".*Driver.*"
        severity: CRITICAL
        advice: |
          🔗 数据库驱动冲突
          当前版本:${versions}
        解决方案:
          1. 统一驱动版本
          2. 移除不需要的数据库驱动
          3. 使用 Spring Boot 管理的版本

4.2 Supported Template Variables

${className}

: conflict class or Jar name (e.g., org.slf4j.Logger) ${conflictType}: type of conflict (e.g., CLASS_DUPLICATE) ${jarCount}: number of conflicting Jars ${jars}: comma‑separated Jar list (e.g., slf4j-api:1.7.36, slf4j-api:2.0.9) ${jarList}: newline‑separated Jar list ${versions}: version list (e.g., 1.7.36, 2.0.9)

4.3 Severity Rules

severity-rules:
  # Critical components
  - patterns: [".*logger.*", ".*driver.*", ".*datasource.*"]
    severity: CRITICAL
    conflict-types: [CLASS_DUPLICATE, VERSION_CONFLICT]
  # Framework components
  - patterns: [".*spring.*", ".*hibernate.*"]
    severity: HIGH
    conflict-types: [VERSION_CONFLICT]
  # Large number of conflicts
  - min-jar-count: 4
    severity: MEDIUM
    conflict-types: [CLASS_DUPLICATE]

5. Real‑World Effect Demonstration

5.1 Detection Result Example

Assume the project contains the following conflicting dependencies:

<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.8.16</version>
</dependency>
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-core</artifactId>
  <version>5.8.19</version>
</dependency>

The detector returns:

{
  "conflicts": [
    {
      "className": "cn.hutool.core.util.StrUtil",
      "type": "CLASS_DUPLICATE",
      "severity": "MEDIUM",
      "conflictingJars": [
        {"name": "hutool-all", "version": "5.8.16"},
        {"name": "hutool-core", "version": "5.8.19"}
      ],
      "advice": "工具库冲突...
解决方案:
1. 选择一个 hutool 版本
2. 排除传递依赖..."
    }
  ],
  "summary": {
    "totalJars": 45,
    "conflictCount": 1,
    "scanTimeMs": 127
  }
}

5.2 Web UI Effect

The web interface shows an overview panel (total JARs, conflict count, scan time), severity distribution (CRITICAL/HIGH/MEDIUM/LOW), and a detailed list of conflicts with class name, conflict type, involved JARs, and remediation advice.

6. Enterprise‑Level Application Scenarios

6.1 Development‑Stage Integration

@Component
public class ConflictDetectionStartupRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        if (isDevelopmentEnvironment()) {
            ScanResult result = performConflictScan();
            if (result.getConflicts().size() > 0) {
                logger.warn("发现 {} 个依赖冲突,建议访问 http://localhost:8080 查看详情", result.getConflicts().size());
            }
        }
    }
}

6.2 CI/CD Pipeline Integration

#!/bin/bash
# Run conflict detection in CI stage
java -jar conflict-detector.jar --mode=ci --output=report.json

# Check conflict count
CONFLICTS=$(cat report.json | jq '.summary.conflictCount')
if [ $CONFLICTS -gt 0 ]; then
  echo "发现 $CONFLICTS 个依赖冲突,请检查报告"
  exit 1
fi

7. Summary

The tool achieves automatic detection and remediation suggestions for Jar conflicts through a configurable rule engine and runtime scanning, providing clear visual feedback in both development and production environments.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backendrule engineConfigurationSpring Bootjar conflictRuntime Scanning
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.