CVE-2025-12183 affects lz4-java 1.8.0, which has out-of-bounds memory operations. The fix exists in version 1.8.1, but the original maintainer at org.lz4:lz4-java did not release it. Instead, Sonatype redirects requests for org.lz4:lz4-java:1.8.1 to a fork at at.yawk.lz4:lz4-java:1.8.1.
This creates a problem: both artifacts declare the same capability (org.lz4:lz4-java), causing Gradle to fail with a capability conflict when both end up on the classpath through transitive dependencies.
The Problem
When a library changes ownership or is forked to a new Maven coordinate, you cannot simply bump the version number. The old coordinate stops receiving updates, and the new coordinate is technically a different artifact. If any transitive dependency still pulls in the old coordinate, Gradle sees two artifacts claiming the same capability and fails.
> Could not resolve all files for configuration ':compileClasspath'.
> Could not resolve org.lz4:lz4-java:1.8.0.
Required by:
project : > some-dependency:1.0.0
> Module 'org.lz4:lz4-java' has been rejected:
Cannot select module with conflict on capability 'org.lz4:lz4-java:1.8.0'
The Solution
Gradle’s capability resolution API lets you explicitly choose which artifact wins when multiple artifacts declare the same capability. Add this to your build configuration:
configurations.all {
resolutionStrategy {
capabilitiesResolution.withCapability("org.lz4:lz4-java") {
select("at.yawk.lz4:lz4-java:0")
}
}
}
The select("at.yawk.lz4:lz4-java:0") tells Gradle to always prefer the forked artifact. The :0 suffix is a version placeholder that matches any version of that artifact.
Implementation in a Shared Plugin
For a multi-project build with shared Gradle plugins, the capability resolution goes in the base Java plugin that all modules apply. In my case, this is java.gradle.kts:
package com.juliusbaer.gos
plugins {
id("java")
// ... other plugins
}
configurations.all {
resolutionStrategy {
preferProjectModules()
// CVE-2025-12183: lz4-java 1.8.0 has out-of-bounds memory operations.
// The fix is in at.yawk.lz4:lz4-java:1.8.1, and Sonatype redirects
// org.lz4:lz4-java:1.8.1 to it. This causes a capability conflict
// that requires explicit resolution.
capabilitiesResolution.withCapability("org.lz4:lz4-java") {
select("at.yawk.lz4:lz4-java:0")
}
}
}
Edge Case: Report Aggregation Plugins
Plugins that aggregate reports across modules (like JaCoCo report aggregation) create their own configurations for resolving dependencies. If these configurations don’t apply the same capability resolution, builds will fail during report generation.
The fix is to ensure the aggregation plugin also applies the base plugin that contains the capability resolution:
// jacoco-report-aggregation.gradle.kts
package com.juliusbaer.gos
plugins {
id("base")
id("com.juliusbaer.gos.java") // Inherits capability resolution
id("com.juliusbaer.gos.repositories")
id("jacoco-report-aggregation")
}
Summary
| Situation | Solution |
|---|---|
| CVE fix released under new Maven coordinate | Use capabilitiesResolution.withCapability() to select the new artifact |
| Multiple modules need the fix | Put the resolution in a shared plugin |
| Report aggregation fails | Ensure aggregation plugins apply the shared plugin with the resolution |
Capability resolution is a powerful Gradle feature for handling these ownership transitions. The alternative - waiting for all transitive dependencies to update - can leave vulnerabilities unpatched for extended periods.