重复资源报错(如meta-inf/manifest.mf冲突)是gradle发现多依赖提供同名文件且未配置处理策略所致,需用packagingoptions明确排除或保留;java项目应改用shadowjar/bootjar或配置jar任务的duplicatesstrategy。

直接说结论:重复资源报错(如 More than one file was found with OS independent path 'META-INF/MANIFEST.MF')不是 Gradle “错了”,而是它在合并依赖时发现多个 JAR/AAR 提供了同名文件,且没被告知怎么处理——你得用 packagingOptions 明确告诉它“留哪个”或“不要哪个”。
为什么 exclude 'META-INF/**' 常被用但不总安全
很多项目一报 META-INF 冲突就无脑加 exclude 'META-INF/**',这确实能过构建,但有风险:
-
META-INF/NOTICE或META-INF/LICENSE被删掉,可能违反开源协议(比如 Apache 2.0 要求保留 NOTICE) -
META-INF/services/下的 SPI 配置被删,会导致ServiceLoader找不到实现类,运行时报NoClassDefFoundError或静默失效 - 某些加密库(如 Bouncy Castle)依赖
META-INF/*.SF签名文件校验,删了会触发SecurityException
更稳妥的做法是只排除确定无用的文件,例如:
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/license.txt'
exclude 'META-INF/license'
exclude 'META-INF/license.md'
exclude 'META-INF/license.md5'
exclude 'META-INF/license.sha1'
}
Java 项目没有 packagingOptions?用 shadowJar 或 bootJar 替代
注意:packagingOptions 是 Android Gradle Plugin(AGP)专属配置,纯 Java Gradle 项目(java-library 或 application 插件)默认不支持。你看到的报错其实来自 jar 任务本身,解决方式不同:
- 用
shadowJar(Gradle Shadow Plugin):它内置merge和transform机制,可精准处理重复资源 - 用 Spring Boot 的
bootJar:自动 mergeMETA-INF/MANIFEST.MF,并提供launchScript和exclude配置项 - 手动改
jar任务:在build.gradle中配置duplicatesStrategy
例如,用标准 jar 任务去重:
tasks.jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
但注意:这种方式会跳过所有重复,不区分文件类型,慎用于含 SPI 或签名的场景。
如何定位到底是哪个依赖带进来的重复文件
光靠报错日志里的路径(如 META-INF/MANIFEST.MF)没法知道来源。必须查依赖树:
- 运行
./gradlew dependencies --configuration runtimeClasspath,找含META-INF的可疑依赖 - 更准的方法:用
./gradlew dependencyInsight --dependency MANIFEST.MF --configuration runtimeClasspath(虽然 MANIFEST.MF 不是依赖名,但可试--dependency META-INF或模糊搜) - 实在不行,直接解压 JAR:用
unzip -l ~/.gradle/caches/modules-2/files-2.1/group.name/artifact/1.0.0/xxx.jar | grep META-INF挨个查
常见“背锅”依赖:旧版 commons-logging、log4j、slf4j、guava,它们常自带冗余 META-INF 文件。
lib/xxx.so 也会出现在 Java 项目里?小心 JNI 依赖混入
Java 项目本不该有 lib/arm64-v8a/xxx.so 这类路径,但如果用了含 JNI 的库(如 net.java.dev.jna:jna、com.github.oshi:oshi-core),它的 JAR 包里可能嵌了 native/ 目录或 lib/ 下的平台相关二进制——这些文件会被 jar 任务一并打包,导致重复冲突。
- 确认是否真需要这些 native 库:多数场景下,JNA 会自动从 classpath 加载对应平台的库,不需要手动打进 fat jar
- 排除方式不是
packagingOptions,而是过滤jar任务的输入:
tasks.jar {
exclude 'lib/**'
exclude 'native/**'
}
或者更细粒度地只排除特定平台:
tasks.jar {
exclude 'lib/win32/**'
exclude 'lib/linux-x86-64/**'
}
关键点在于:Java 项目的资源冲突根源不在打包策略本身,而在于你是否意识到某些依赖“悄悄带了不该带的东西”。别让一个 exclude 解决方案掩盖了对依赖真实内容的忽视。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











