Gradle Tasks, Dependencies, and Plugins
Gradle’s power comes from its flexible task system, fine-grained dependency configurations, and rich plugin ecosystem. This chapter covers the features you’ll use daily.
Built-In Tasks
./gradlew tasks --all # list everything
# Common tasks
./gradlew build # compile + test + jar
./gradlew clean # delete build/
./gradlew test # run tests
./gradlew jar # create JAR
./gradlew compileJava # compile main sources
./gradlew dependencies # show dependency tree
Custom Tasks
// Groovy DSL
tasks.register('hello') {
group = 'custom'
description = 'Prints a greeting'
doLast {
println 'Hello from Gradle!'
}
}
// Kotlin DSL
tasks.register("hello") {
group = "custom"
description = "Prints a greeting"
doLast {
println("Hello from Gradle!")
}
}
$ ./gradlew hello
> Task :hello
Hello from Gradle!
Task Dependencies
tasks.register('generateCode') {
doLast { println 'Generating code...' }
}
tasks.register('compileGenerated') {
dependsOn('generateCode')
doLast { println 'Compiling generated code...' }
}
tasks.register('packageAll') {
dependsOn('compileGenerated', 'jar')
doLast { println 'Packaging everything...' }
}
$ ./gradlew packageAll
> Task :generateCode
Generating code...
> Task :compileGenerated
Compiling generated code...
> Task :jar
> Task :packageAll
Packaging everything...
Task Types
// Copy task
tasks.register('copyDocs', Copy) {
from 'src/docs'
into 'build/docs'
}
// Delete task
tasks.register('cleanLogs', Delete) {
delete 'logs/'
}
// Exec task
tasks.register('runApp', JavaExec) {
classpath = sourceSets.main.runtimeClasspath
mainClass = 'com.myapp.App'
args = ['--port=8080']
}
Dependency Configurations
| Configuration | Compile Classpath | Runtime Classpath | Exposed to Consumers | Maven Equivalent |
|---|---|---|---|---|
implementation |
✅ | ✅ | ❌ | compile |
api |
✅ | ✅ | ✅ | compile |
compileOnly |
✅ | ❌ | ❌ | provided |
runtimeOnly |
❌ | ✅ | ❌ | runtime |
testImplementation |
test only | test only | ❌ | test |
testRuntimeOnly |
❌ | test runtime | ❌ | test |
dependencies {
// Your code uses this — not exposed to downstream projects
implementation 'com.google.guava:guava:33.2.1-jre'
// Your code uses this AND downstream projects need it too
api 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
// Available at compile time, not packaged (container provides)
compileOnly 'jakarta.servlet:jakarta.servlet-api:6.1.0'
// Not needed at compile time, needed at runtime
runtimeOnly 'org.postgresql:postgresql:42.7.3'
// Testing only
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3'
testImplementation 'org.mockito:mockito-core:5.12.0'
}
implementation vs api
Library A (uses api for jackson):
└── jackson-databind (api)
App depends on Library A:
└── Can see jackson-databind ✅ (because api)
Library B (uses implementation for guava):
└── guava (implementation)
App depends on Library B:
└── Cannot see guava ❌ (because implementation)
Use
implementationby default. Useapionly when consumers need the dependency.
Dependency Tree
./gradlew dependencies --configuration runtimeClasspath
Output:
runtimeClasspath
+--- com.google.guava:guava:33.2.1-jre
| +--- com.google.guava:failureaccess:1.0.2
| \--- com.google.errorprone:error_prone_annotations:2.28.0
+--- org.postgresql:postgresql:42.7.3
\--- com.fasterxml.jackson.core:jackson-databind:2.17.1
+--- com.fasterxml.jackson.core:jackson-core:2.17.1
\--- com.fasterxml.jackson.core:jackson-annotations:2.17.1
Version Catalogs (libs.versions.toml)
Centralized version management (Gradle 7.4+):
gradle/libs.versions.toml:
[versions]
junit = "5.10.3"
mockito = "5.12.0"
jackson = "2.17.1"
spring-boot = "3.3.0"
[libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
build.gradle.kts:
dependencies {
implementation(libs.jackson.databind)
testImplementation(libs.junit.jupiter)
testImplementation(libs.mockito.core)
}
Version catalogs are the modern way to manage versions in Gradle — like Maven’s BOM.
Applying Plugins
// Groovy DSL
plugins {
id 'java'
id 'application'
id 'org.springframework.boot' version '3.3.0'
id 'io.spring.dependency-management' version '1.1.5'
}
// Kotlin DSL
plugins {
java
application
id("org.springframework.boot") version "3.3.0"
id("io.spring.dependency-management") version "1.1.5"
}
Spring Boot Gradle Plugin
./gradlew bootRun # run Spring Boot app
./gradlew bootJar # create executable JAR
Publishing Artifacts
plugins {
id 'java-library'
id 'maven-publish'
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
groupId = 'com.myapp'
artifactId = 'my-library'
version = '1.0.0'
}
}
repositories {
maven {
url = uri('https://nexus.mycompany.com/repository/maven-releases/')
credentials {
username = findProperty('nexusUser')
password = findProperty('nexusPass')
}
}
}
}
./gradlew publish
Common Mistakes
| Mistake | Fix |
|---|---|
Using api everywhere |
Use implementation by default — api leaks dependencies |
Missing useJUnitPlatform() |
JUnit 5 tests won’t be discovered |
| Hardcoded versions in every module | Use version catalogs (libs.versions.toml) |
Not using --configuration with dependency tree |
Default shows everything — filter to runtimeClasspath |
Key Takeaways
- Custom tasks with
tasks.register— usedependsOnfor ordering. implementation= private dependency.api= exposed to consumers.- Version catalogs (
libs.versions.toml) centralize version management. - Spring Boot plugin adds
bootRunandbootJartasks. ./gradlew dependenciesshows the full dependency tree.
What’s Next?
Next: Gradle vs Maven — a comprehensive comparison to help you choose the right tool.