Most build changes are dependency changes; using the right configuration keeps compile classpaths lean and APIs clean.
Dependencies are declared in the dependencies { } block as "group:name:version" coordinates, assigned to a configuration that says when they are needed:
implementation: needed to compile and run your code, but not exposed to consumers of your library. The default choice.api(from thejava-libraryplugin): part of your public API; consumers get it on their compile classpath too.compileOnly: available at compile time only (annotations, provided servers).runtimeOnly: only at run time (JDBC drivers, logging backends).testImplementation,testRuntimeOnly: the same for tests.annotationProcessorfor tools like Lombok or MapStruct.
Gradle downloads artifacts from the repositories { } you list (mavenCentral(), google(), mavenLocal(), or a company Nexus/Artifactory) and caches them in ~/.gradle/caches.
Transitive dependencies come along automatically. When two paths need different versions of the same library, Gradle picks the highest version by default (not "nearest" like Maven). See the result with ./gradlew dependencies or ./gradlew dependencyInsight --dependency jackson-databind.
Dynamic versions (1.+) and latest.release make builds non-reproducible; prefer exact versions. Rich versions (strictly, prefer) and constraints { } adjust transitive versions without declaring a direct dependency. Project dependencies use implementation(project(":lib")).
Example
plugins { `java-library` }
repositories { mavenCentral() }
dependencies {
api("com.fasterxml.jackson.core:jackson-databind:2.18.3") // leaks into consumers' compile classpath
implementation("org.slf4j:slf4j-api:2.0.17") // internal
compileOnly("org.projectlombok:lombok:1.18.36")
annotationProcessor("org.projectlombok:lombok:1.18.36")
runtimeOnly("ch.qos.logback:logback-classic:1.5.18") // logging backend, not compiled against
implementation(project(":core")) // another subproject
constraints {
implementation("com.google.guava:guava:33.4.0-jre") { because("CVE fix") }
}
testImplementation("org.junit.jupiter:junit-jupiter:5.11.4")
}
// $ ./gradlew dependencies --configuration compileClasspath
// $ ./gradlew dependencyInsight --dependency slf4j-apiRemember
- implementation hides a dependency from consumers; api exposes it
- compileOnly/runtimeOnly split compile and run time
- Gradle picks the highest version among conflicting transitive versions
- dependencyInsight explains why a version was chosen
Watch out for
- Using api for everything, which drags your internals onto every consumer's compile classpath.