Groovy Gradle Integration — Build Automation and Custom Tasks
In this tutorial, you will learn about Groovy Gradle Integration. We cover key concepts, practical examples, and best practices to help you master this topic.
Groovy is the default scripting language for Gradle build automation, enabling custom tasks, plugins, and build logic for Android and Java projects with concise DSL syntax.
What You'll Learn
- Writing Gradle build scripts in Groovy
- Creating custom tasks and plugins
- Dependency management
- Build lifecycle configuration
Why It Matters
Every Android and Java project uses Gradle for building, testing, and deploying. Doda Browser uses Gradle with Groovy scripts to manage its multi-module build pipeline across Android, desktop, and server targets.
Real-World Use
Android app builds, Java library publishing, multi-module project configuration, continuous integration pipelines, and custom plugin development all rely on Gradle's Groovy DSL.
flowchart LR
A["Gradle Build"] --> B["Projects"]
B --> C["Tasks"]
C --> D["Plugins"]
D --> E["Artifacts"]
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Gradle Build Script Basics
A Gradle build script is a Groovy file named build.gradle:
plugins {
id 'java'
id 'application'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.guava:guava:32.1.3-jre'
testImplementation 'junit:junit:4.13.2'
}
application {
mainClass = 'com.example.Main'
}
Run this with gradle build or gradle run.
Custom Tasks
Define tasks with the tasks.register syntax:
tasks.register('hello') {
doLast {
println 'Hello from Gradle!'
}
}
tasks.register('printVersion') {
doLast {
println "Version: ${project.version}"
}
}
Execute with gradle hello or gradle printVersion.
Task Dependencies and Ordering
tasks.register('taskA') {
doLast { println 'Task A' }
}
tasks.register('taskB') {
dependsOn tasks.named('taskA')
doLast { println 'Task B' }
}
tasks.register('taskC') {
dependsOn tasks.named('taskB')
finalizedBy tasks.named('cleanup')
doLast { println 'Task C' }
}
tasks.register('cleanup') {
doLast { println 'Cleanup done' }
}
Running gradle taskC executes A, B, C, then cleanup.
Custom Plugin Development
class VersionPlugin implements Plugin<Project> {
void apply(Project project) {
project.ext {
major = 1
minor = 0
patch = 0
}
project.tasks.register('showVersion') {
doLast {
println "${project.ext.major}.${project.ext.minor}.${project.ext.patch}"
}
}
}
}
apply plugin: VersionPlugin
Plugins encapsulate reusable build logic across projects.
Multi-Module Projects
// settings.gradle
rootProject.name = 'my-app'
include 'core', 'web', 'mobile'
// core/build.gradle
dependencies {
implementation 'com.google.inject:guice:5.1.0'
}
// web/build.gradle
dependencies {
implementation project(':core')
implementation 'org.eclipse.jetty:jetty-server:11.0.16'
}
// mobile/build.gradle
dependencies {
implementation project(':core')
}
Multi-module builds scale to large codebases with clear separation.
Dependency Configurations
dependencies {
implementation 'org.apache.commons:commons-lang3:3.13.0'
api 'com.google.code.gson:gson:2.10.1'
compileOnly 'org.projectlombok:lombok:1.18.30'
runtimeOnly 'ch.qos.logback:logback-classic:1.4.11'
testImplementation 'org.mockito:mockito-core:5.6.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
}
Each configuration serves a specific scope in the build lifecycle.
Build Lifecycle Hooks
gradle.beforeProject { project ->
println "Configuring ${project.name}"
}
gradle.afterProject { project ->
println "Finished ${project.name}"
}
gradle.taskGraph.whenReady { graph ->
println "Tasks to execute: ${graph.allTasks.size()}"
}
gradle.buildFinished { result ->
println "Build ${result.failure ? 'failed' : 'succeeded'}"
}
Lifecycle hooks enable instrumentation and cross-cutting concerns.
Common Mistakes
1. Using implementation instead of api
implementation hides transitive dependencies. Use api when consumers need the dependency at compile time.
2. Forgetting to declare repositories
Gradle does not assume any Repository. Always add mavenCentral() or jcenter() before declaring dependencies.
3. Modifying task actions after registration
tasks.register('myTask') {
doLast { println 'First' }
}
// Wrong — does nothing
myTask.doLast { println 'Second' }
// Right
tasks.named('myTask').configure {
doLast { println 'Second' }
}
4. Using compile instead of implementation
The compile configuration is deprecated since Gradle 3.0. Use implementation or api.
5. Not using the configuration cache
// Enable in gradle.properties
org.gradle.configuration-cache=true
Without it, every build re-executes configuration logic unnecessarily.
Practice Questions
1. What does the implementation configuration do?
Declares a dependency that is available at compile and runtime for the current module but not exposed to consumers.
2. How do you define a task dependency?
Use dependsOn tasks.named('taskName') inside the task registration block.
3. What is the difference between doFirst and doLast?
doFirst appends an action to the beginning of the task action list, doLast appends to the end.
4. How do you create a multi-module Gradle project?
List subproject directories in settings.gradle with include 'module-a', 'module-b' and give each its own build.gradle.
Challenge: Write a Gradle plugin that generates a build report HTML file with build time, dependency list, and test results.
Solution
class BuildReportPlugin implements Plugin<Project> {
void apply(Project project) {
project.tasks.register('buildReport') {
doLast {
def report = new File(project.buildDir, 'reports/build.html')
report.parentFile.mkdirs()
report.text = """
<html><body>
<h1>Build Report</h1>
<p>Project: ${project.name}</p>
<p>Dependencies:</p>
<ul>
${project.configurations.compileClasspath.allDependencies.collect {
"<li>${it.group}:${it.name}:${it.version}</li>"
}.join('\n')}
</ul>
</body></html>
"""
}
}
}
}
FAQ
{{< faq question="Can I use Kotlin DSL instead of Groovy for Gradle?" >}} Yes, Gradle supports both Groovy DSL (build.gradle) and Kotlin DSL (build.gradle.kts). Kotlin DSL offers better IDE support and type safety. {{< /faq >}}
{{< faq question="What is Gradle wrapper?" >}} The Gradle wrapper (gradlew) is a script that downloads and runs a specific Gradle version, ensuring all developers use the same version without manual installation. {{< /faq >}}
{{< faq question="How do I exclude transitive dependencies?" >}}
Use exclude inside the dependency block: implementation('com.example:lib:1.0') { exclude group: 'unwanted-group' }.
{{< /faq >}}
{{< faq question="What is the difference between apply plugin and plugins block?" >}}
The plugins block (DSL) is the modern approach with type-safe accessors. The apply plugin syntax is legacy but still works.
{{< /faq >}}
{{< faq question="How do I publish a library with Gradle?" >}}
Apply the maven-publish plugin and configure the publishing block with repositories and publications.
{{< /faq >}}
Mini Project
Create a Gradle build script for a library that compiles, runs tests, generates Javadoc, and publishes to a local Maven repository.
plugins {
id 'java-library'
id 'maven-publish'
}
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
maven(MavenPublication) {
from components.java
}
}
repositories {
mavenLocal()
}
}
What's Next
Now that you understand Gradle integration, proceed to learn about Groovy's Meta-Object Protocol.
| Topic | Description | Link |
|---|---|---|
| MOP | Meta-Object Protocol | {{< ref "18-meta-object-protocol" >}} |
| Testing | Spock testing framework | {{< ref "15-testing" >}} |
| Java | JVM ecosystem comparison | Java |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro