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

SituationSolution
CVE fix released under new Maven coordinateUse capabilitiesResolution.withCapability() to select the new artifact
Multiple modules need the fixPut the resolution in a shared plugin
Report aggregation failsEnsure 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.