Declaring Dependencies Basics
Types of dependencies
There are three main types of dependencies in Gradle:
-
Module Dependencies: Refer to libraries from external repositories.
-
Project Dependencies: Refer to other projects in the same multi-project build.
-
File Dependencies: Refer to local files or directories, such as
.jar
or.aar
files.
1. Module dependencies
Module dependencies are the most common dependencies. They refer to a dependency that is identified by module coordinates (group, name, and version):
dependencies {
runtimeOnly(group = "org.springframework", name = "spring-core", version = "2.5")
runtimeOnly("org.springframework:spring-aop:2.5")
runtimeOnly("org.hibernate:hibernate:3.0.5") {
isTransitive = true
}
runtimeOnly(group = "org.hibernate", name = "hibernate", version = "3.0.5") {
isTransitive = true
}
}
dependencies {
runtimeOnly group: 'org.springframework', name: 'spring-core', version: '2.5'
runtimeOnly 'org.springframework:spring-core:2.5',
'org.springframework:spring-aop:2.5'
runtimeOnly(
[group: 'org.springframework', name: 'spring-core', version: '2.5'],
[group: 'org.springframework', name: 'spring-aop', version: '2.5']
)
runtimeOnly('org.hibernate:hibernate:3.0.5') {
transitive = true
}
runtimeOnly group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
runtimeOnly(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
transitive = true
}
}
Gradle offers multiple notations for declaring module dependencies, including string notation and map notation.
-
String Notation: Simplifies dependency declaration by combining the group, name, and version into a single string.
-
Map Notation: Allows for specifying each part of the coordinates separately.
For advanced configurations, such as enforcing strict versions, you can also provide a closure when alongside these notations.
2. Project dependencies
Project dependencies allow you to reference other projects within a multi-project Gradle build.
This is useful for organizing large projects into smaller, modular components:
dependencies {
implementation(project(":utils"))
implementation(project(":api"))
}
dependencies {
implementation project(':utils')
implementation project(':api')
}
Gradle uses the project()
function to define a project dependency.
This function takes the relative path to the target project within the build.
The path is typically defined using a colon (:
) to separate different levels of the project structure.
Project dependencies are automatically resolved such that the dependent project is always built before the project that depends on it.
Type-safe project dependencies
Type-safe project accessors are an incubating feature which must be enabled explicitly. Implementation may change at any time.
To add support for type-safe project accessors, add enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
this to your settings.gradle(.kts)
file:
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS'
One downside of using the project(":some:path")
notation is the need to remember project paths for dependencies.
Moreover, changing a project path requires manually updating every occurrence, increasing the risk of missing one.
Instead, the experimental type-safe project accessors API provides IDE completion, making it easier to declare dependencies:
dependencies {
implementation(projects.utils)
implementation(projects.api)
}
dependencies {
implementation projects.utils
implementation projects.api
}
With this API, incorrectly specified projects in Kotlin DSL scripts trigger compilation errors, helping you avoid missed updates.
Project accessors are based on project paths.
For instance, the path :commons:utils:some:lib
becomes projects.commons.utils.some.lib
, while kebab-case (some-lib
) and snake-case (some_lib
) are converted to camel case: projects.someLib
.
3. File dependencies
File dependencies allow you to include external JARs or other files directly into your project by referencing their file paths. File dependencies also allow you to add a set of files directly to a configuration without using a repository.
File dependencies are generally discouraged.
Instead, prefer declaring dependencies on an external repository, or if necessary, declaring a maven or ivy repository using a file:// URL.
|
File dependencies are unique because they represent a direct reference to files on the filesystem without any associated metadata, such as transitive dependencies, origin, or author information.
configurations {
create("antContrib")
create("externalLibs")
create("deploymentTools")
}
dependencies {
"antContrib"(files("ant/antcontrib.jar"))
"externalLibs"(files("libs/commons-lang.jar", "libs/log4j.jar"))
"deploymentTools"(fileTree("tools") { include("*.exe") })
}
configurations {
antContrib
externalLibs
deploymentTools
}
dependencies {
antContrib files('ant/antcontrib.jar')
externalLibs files('libs/commons-lang.jar', 'libs/log4j.jar')
deploymentTools(fileTree('tools') { include '*.exe' })
}
In this example, each dependency explicitly specifies its location within the file system. Common methods for referencing these files include:
-
Project.files()
: Accepts one or more file paths directly. -
ProjectLayout.files()
: Accepts one or more file paths directly. -
Project.fileTree()
: Defines a directory and includes or excludes specific file patterns.
The order of files in a |
Alternatively, you can use a flat directory repository to specify the source directory for multiple file dependencies.
Ideally, you should use Maven or Ivy repository with a local URL:
repositories {
maven {
url 'file:///path/to/local/files' // Replace with your actual path
}
}
To add files as dependencies, pass a file collection to the configuration:
dependencies {
runtimeOnly(files("libs/a.jar", "libs/b.jar"))
runtimeOnly(fileTree("libs") { include("*.jar") })
}
dependencies {
runtimeOnly files('libs/a.jar', 'libs/b.jar')
runtimeOnly fileTree('libs') { include '*.jar' }
}
Note that file dependencies are not included in the published dependency descriptor for your project. However, they are available in transitive dependencies within the same build, meaning they can be used within the current build but not outside it.
You should specify which tasks produce the files for a file dependency. Otherwise, the necessary tasks might not run when you depend on them transitively from another project:
dependencies {
implementation(files(layout.buildDirectory.dir("classes")) {
builtBy("compile")
})
}
tasks.register("compile") {
doLast {
println("compiling classes")
}
}
tasks.register("list") {
val compileClasspath: FileCollection = configurations["compileClasspath"]
dependsOn(compileClasspath)
doLast {
println("classpath = ${compileClasspath.map { file: File -> file.name }}")
}
}
dependencies {
implementation files(layout.buildDirectory.dir('classes')) {
builtBy 'compile'
}
}
tasks.register('compile') {
doLast {
println 'compiling classes'
}
}
tasks.register('list') {
FileCollection compileClasspath = configurations.compileClasspath
dependsOn compileClasspath
doLast {
println "classpath = ${compileClasspath.collect { File file -> file.name }}"
}
}
$ gradle -q list compiling classes classpath = [classes]
Gradle distribution-specific dependencies
Gradle API dependency
You can declare a dependency on the API of the current version of Gradle by using the DependencyHandler.gradleApi() method. This is useful when you are developing custom Gradle tasks or plugins:
dependencies {
implementation(gradleApi())
}
dependencies {
implementation gradleApi()
}
Gradle TestKit dependency
You can declare a dependency on the TestKit API of the current version of Gradle by using the DependencyHandler.gradleTestKit() method. This is useful for writing and executing functional tests for Gradle plugins and build scripts:
dependencies {
testImplementation(gradleTestKit())
}
dependencies {
testImplementation gradleTestKit()
}
Local Groovy dependency
You can declare a dependency on the Groovy that is distributed with Gradle by using the DependencyHandler.localGroovy() method. This is useful when you are developing custom Gradle tasks or plugins in Groovy:
dependencies {
implementation(localGroovy())
}
dependencies {
implementation localGroovy()
}
Documenting dependencies
When declaring a dependency or a dependency constraint, you can provide a reason to clarify why the dependency is included. This helps make your build script and the dependency insight report easier to interpret:
plugins {
`java-library`
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.ow2.asm:asm:7.1") {
because("we require a JDK 9 compatible bytecode generator")
}
}
plugins {
id 'java-library'
}
repositories {
mavenCentral()
}
dependencies {
implementation('org.ow2.asm:asm:7.1') {
because 'we require a JDK 9 compatible bytecode generator'
}
}
In this example, the because()
method provides a reason for including the asm
library, which helps explain its purpose in the context of the build:
$ gradle -q dependencyInsight --dependency asm org.ow2.asm:asm:7.1 Variant compile: | Attribute Name | Provided | Requested | |--------------------------------|----------|--------------| | org.gradle.status | release | | | org.gradle.category | library | library | | org.gradle.libraryelements | jar | classes | | org.gradle.usage | java-api | java-api | | org.gradle.dependency.bundling | | external | | org.gradle.jvm.environment | | standard-jvm | | org.gradle.jvm.version | | 11 | Selection reasons: - Was requested: we require a JDK 9 compatible bytecode generator org.ow2.asm:asm:7.1 \--- compileClasspath A web-based, searchable dependency report is available by adding the --scan option.