Gradle provides a rich and flexible model for declaring dependencies, managing versions, and resolving conflicts across builds.

gradle basic 13

Declare Dependencies

The dependencies{} block is where you declare the external libraries, internal modules, or files your project needs to compile, run, or test:

build.gradle.kts
dependencies {
    implementation("com.google.guava:guava:30.0-jre")
    runtimeOnly("org.apache.commons:commons-lang3:3.14.0")
}
build.gradle
dependencies {
    implementation("com.google.guava:guava:30.0-jre")
    runtimeOnly("org.apache.commons:commons-lang3:3.14.0")
}

Each dependency is added to a bucket configuration. For example implementation, runtimeOnly, or testImplementation. Bucket configurations defines where that dependency is used (compile classpath, runtime only, tests, etc.). The set of configurations available depends on the plugins you apply (e.g., java/java-library, Android Gradle Plugin (AGP), Kotlin Multiplatform (KMP), etc.).

Gradle recommends the single-string notation for external modules. The map notation is deprecated as of Gradle 9.1.0 and will fail your build in Gradle 10:

dependencies {
    // GOOD: single-string notation
    implementation("com.google.guava:guava:32.1.2-jre")
    // BAD: map notation
    implementation(group = "com.google.guava", name = "guava", version = "32.1.2-jre")
}

Centralize Versions with Version Catalogs

Gradle recommends using version catalogs to declare dependency versions in a single, reusable location:

gradle/libs.versions.toml
[versions]
guava = "33.3.1-jre"
junit-jupiter = "5.11.3"

[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }

You can then use these aliases in your build scripts:

app/build.gradle.kts
dependencies {  (2)
    // Use JUnit Jupiter for testing.
    testImplementation(libs.junit.jupiter)

    testRuntimeOnly("org.junit.platform:junit-platform-launcher")

    // This dependency is used by the application.
    implementation(libs.guava)
}
app/build.gradle
dependencies {  (2)
    // Use JUnit Jupiter for testing.
    testImplementation libs.junit.jupiter

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // This dependency is used by the application.
    implementation libs.guava
}

Enforce and Constrain Versions

Gradle allows you to constrain dependency versions to avoid unwanted upgrades or enforce known good versions:

build.gradle.kts
dependencies {
    implementation("org.apache.httpcomponents:httpclient:4.5.4")
    implementation("commons-codec:commons-codec") {
        version {
            strictly("1.9")
        }
    }
}
build.gradle
dependencies {
    implementation("org.apache.httpcomponents:httpclient:4.5.4")
    implementation("commons-codec:commons-codec") {
        version {
            strictly("1.9")
        }
    }
}

You can also constrain a module globally:

build.gradle.kts
dependencies {
    implementation("org.apache.httpcomponents:httpclient")
    constraints {
        implementation("org.apache.httpcomponents:httpclient:4.5.3") {
            because("previous versions have a bug impacting this application")
        }
        implementation("commons-codec:commons-codec:1.11") {
            because("version 1.9 pulled from httpclient has bugs affecting this application")
        }
    }
}
build.gradle
dependencies {
    implementation('org.apache.httpcomponents:httpclient')
    constraints {
        implementation('org.apache.httpcomponents:httpclient:4.5.3') {
            because('previous versions have a bug impacting this application')
        }
        implementation('commons-codec:commons-codec:1.11') {
            because('version 1.9 pulled from httpclient has bugs affecting this application')
        }
    }
}

Resolve Conflicts with Capabilities

Sometimes multiple libraries provide the same functionality under different coordinates. This can lead to classpath conflicts:

dependencies {
    implementation("jaxen:jaxen:1.1.6")     // Transitive dependency that brings XPath functionality
    implementation("org.jdom:jdom2:2.0.6")  // Also offers XPath functionality
}

Gradle lets you model these cases using capabilities. For example:

dependencies {
    implementation("jaxen:jaxen:1.1.6") {
        capabilities {
            requireCapability("xml:xpath-support")
        }
    }
    implementation("org.jdom:jdom2:2.0.6")
}

Then declare capabilities via a component metadata rule:

components {
    withModule("jaxen:jaxen") {
        allVariants {
            withCapabilities {
                addCapability("xml", "xpath-support", "1.0")
            }
        }
    }
    withModule("org.jdom:jdom2") {
        allVariants {
            withCapabilities {
                addCapability("xml", "xpath-support", "1.0")
            }
        }
    }
}

Gradle will now treat jaxen and jdom2 as alternate implementations and select the one with a required capability.

There are many more ways to influence dependency resolution in Gradle. Consult the Dependency Management chapter to learn more.