Gradle in Java
Build scripts, tasks, configurations like implementation vs api.
Builds as code
Gradle builds are scripts (Kotlin or Groovy DSL) that configure tasks such as compileJava, test and jar, wired into a dependency graph. Running test runs compileJava first, because test depends on it.
Configurations
Dependencies go into configurations. **implementation: used internally, hidden from consumers. api** (from the java-library plugin): part of your public API, exposed to consumers. **compileOnly: needed to compile, not shipped. runtimeOnly, and testImplementation** for test code only.
plugins { `java-library` }
dependencies {
api("com.acme:money:2.1.0")
implementation("com.acme:http:1.4.0")
testImplementation("com.acme:fakes:1.0.0")
}A type in your public API
implementation("com.acme:money:2.1.0")
// public Money total() { ... }Consumers get 'cannot find symbol': Money isn't on their compile classpath.
api("com.acme:money:2.1.0")
// public Money total() { ... }Types in your public signatures must be visible to consumers.
Why so fast the second time?
You run ./gradlew test twice with no changes. The second run finishes almost instantly. Why?
Think about it, then reveal the answer
Gradle tracks each task's inputs and outputs. If nothing changed, the task is UP-TO-DATE and skipped. Change one source file and only the affected tasks run again; the build cache can even reuse outputs from earlier builds.
The wrapper
The Gradle wrapper (gradlew) lets everyone build without installing Gradle. The script downloads the exact version pinned in gradle-wrapper.properties, so laptops and CI behave the same.
Translating from Maven
Maven has the provided scope. Which Gradle configuration plays a similar role?
compileOnlyprovidedruntimeOnly
Show the answer
**compileOnly**: available when compiling, not shipped at runtime. provided is a Maven scope; Gradle's Java plugins have no configuration with that name.
Why implementation is the default choice
In a big multi-module build, implementation keeps consumers' classpaths small and means they don't need recompiling when your internals change. Reach for api only for types that really appear in your public signatures.
Key takeaways
- implementation: hidden from consumers' compile classpath
- api (java-library plugin): exposed to consumers
- The wrapper (gradlew) pins the Gradle version
- Unchanged tasks are skipped as UP-TO-DATE
Gradle is the official build tool for Android apps, so every Android Studio project comes with a gradlew wrapper.
Practice questions
Your library's public method returns a type from library X, declared with `implementation`. Consumers get 'cannot find symbol' for X's type. Fix?
- Declare X with compileOnly
- Shade X into your JAR
- Declare X with `api` instead of `implementation`
- Move X to testImplementation
Check your answer
Declare X with `api` instead of `implementation`. Types that appear in your public signatures must be visible to consumers at compile time, which is what api does.
Running `./gradlew test` a second time with no changes finishes almost instantly. Why?
- Tasks whose inputs and outputs haven't changed are UP-TO-DATE and skipped
- The JVM caches test results in metaspace
- Gradle only runs failed tests
- Gradle skips tests on the second run by default
Check your answer
Tasks whose inputs and outputs haven't changed are UP-TO-DATE and skipped. Gradle tracks each task's inputs and outputs. Change a source file and only the affected tasks run again.