How to use Gradle api vs. implementation dependencies with the Java Library plugin

Gradle api vs implementation dependencies

Last Updated on November 25, 2022

In this article you’ll learn the main differences between Gradle api and implementation configurations with a real-world example showing exactly how things work under the covers.

The Java Library Plugin is the recommended plugin to use when building libraries to be consumed by another project. It distinguishes between api and implementation dependencies, offering some key benefits for whoever consumes the library.

UPDATED in July 2021 to use the latest Gradle version.

1. Gradle dependency configuration basics

In Gradle a configuration represents a group of artifacts that you want to use in some way in your build. A dependency, on the other hand, is a reference to a single artifact that is grouped in a configuration.

dependencies {
    implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.2'

For example, here we have a dependency on jackson-databind within the implementation configuration. This configuration will get used to generate both compile and runtime classpaths, which you can query using ./gradlew dependencies.

Info: in Gradle 7+ the compile configuration was removed, and should be replaced with implementation

Here’s the compile classpath:

compileClasspath - Compile classpath for source set 'main'.
\--- com.fasterxml.jackson.core:jackson-databind:2.10.2
     +--- com.fasterxml.jackson.core:jackson-annotations:2.10.2
     \--- com.fasterxml.jackson.core:jackson-core:2.10.2

And the runtime classpath:

runtimeClasspath - Runtime classpath of source set 'main'.
\--- com.fasterxml.jackson.core:jackson-databind:2.10.2
     +--- com.fasterxml.jackson.core:jackson-annotations:2.10.2
     \--- com.fasterxml.jackson.core:jackson-core:2.10.2

These classpaths include not only the artifact we depended on but also its transitive dependencies.

In this case the classpaths are exactly the same. This is because the jackson-databind artifact has declared its own dependencies as compile scope dependencies, which you can see in the Maven Central repository:

And according to the Maven docs, anything in the compile scope is also included in the runtime scope

Compile dependencies are available in all classpaths of a project.

But what if we wanted to have more fine-grained control over the compile and runtime classpaths?

Wouldn’t it be nice if only artifacts that really need to be on the compile classpath are added to it, and all the others go on the runtime classpath?

Can Gradle provide a way to do this? 😉

2. Benefits of fine-grained classpath control

So let’s imagine a scenario where you have an Application that depends on Library A which has two transitive dependencies, Library B & Library C:

Some additional information:

  1. Application uses classes from Library A
  2. Library A uses classes from Library B and Library C
  3. Library A exposes Library B on its interface (e.g. one of it’s classes could return a type defined in Library B)
  4. Library C is only used internally of Library A (e.g. inside methods)

Application binary interface (ABI)

What’s alluded to in points 3 & 4 above is what’s known as the library binary interface or application binary interface (ABI). Some types that fall into the ABI include:

  • public method parameters
  • return types
  • types used in parent classes or interfaces

Types that don’t fall into the ABI include:

  • types used in method bodies
  • types defined in private method declarations

The important thing to remember about the ABI is that any types used within it need to be declared on the compile classpath. With this information then, we can think about how we’d build up the compile and runtime classpaths for Application:

Compile classpath
  • Library A, as we interact directly with this
  • Library B, as Library A exposes this on its interface that we interact with
Runtime classpath
  • Library A
  • Library B
  • Library C, as this is used internally in Library A

If we had the ability to build up these classpaths selectively like this then, we’d benefit from the following:

  1. cleaner classpaths
  2. won’t accidentally use a library that we haven’t depended on explicitly e.g. can’t use Library C in Application as it’s not on the compile classpath
  3. faster compilation due to our cleaner classpath
  4. less recompilation as when artifacts on the runtime classpath change we don’t need to recompile

3. The Java Library Gradle plugin makes this possible

The Java Library Gradle plugin makes this fine-grained classpath control possible. It’s up to you as the creator of a library to define which dependencies should be included in the runtime or compile classpaths of whatever application is consuming this library.

We achieve this with the following dependency configurations:

  • api – dependencies in the api configuration are part of the ABI of the library we’re writing and therefore should appear on the compile and runtime classpaths
  • implementation – dependencies in the implementation configuration aren’t part of the ABI of the library we’re writing. They will appear only on the runtime classpath.

Going back to our example from before then, if we were writing Library A our dependencies in our build.gradle would look something like this:

dependencies {
    api 'library-b'
    implementation 'library-c'

This enables the Java Library plugin to generate the artifact with the relevant information so that the consumer can construct the classpath correctly.

Get the Build Boss newsletter

Found this website helpful? Subscribe for updates.

✅ All of my latest articles each week
✅ Access to video tutorials
✅ Exclusive tips and offers not found on my website

Please wait...

Thank you, your sign up request was successful! I'll be in touch soon. Tom.

4. Real world example

Enough of this Library A-Library B stuff then! Let’s get into a real life example and see what’s going on under the hood.

We’re going to generate 2 projects:

  1. a library (called gradle-java-library-plugin-library in GitHub): this is the library we’ll build with the Java Library plugin
  2. a consumer (called gradle-java-library-plugin-consumer in GitHub): this is the application that will depend on the library

A library using the Java Library plugin

Our build.gradle looks like this:

plugins {
    id 'java-library'
    id 'maven-publish'

group = 'com.tomgregory'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {

dependencies {
    implementation group: '', name: 'google-http-client', version: '1.34.2'
    api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.2'

publishing {
    publications {
        maven(MavenPublication) {
  • we’re applying the Java Library plugin as already discussed
  • we’re also applying the Maven Publish plugin as we’ll need to publish to Maven local so that our consumer can pull the artifact. A publishing block is included at the end to set this up properly.
  • we have an implementation dependency on google-http-client. Remember this means our consumer will have only a runtime dependency on this artifact, so it cannot appear in the ABI of this library.
  • we have an api dependency on com.fasterxml.jackson.core. This means our consumer will have both a compile and runtime dependency on this artifact. It can therefore appear in the ABI of this library.

Our library has just one class, AwesomeService, which we’ll put in a package com.tomgregory:

package com.tomgregory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;


public class AwesomeService {
    private ObjectMapper objectMapper = new ObjectMapper();

    public JsonNode getWebPage() throws IOException {
        HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory();
        HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(""));
        String response =  request.execute().parseAsString();

        return objectMapper.readValue(response, JsonNode.class);

This class exposes a method that hits a REST API and returns the result. Note that:

  • the method returns JsonNode which is part of the com.fasterxml.jackson.core dependency. This return type forms part of this class’s ABI.
  • the method body only uses the google-http-client library to make an HTTP request. google-http-client is therefore not part of the ABI of this class.

Publishing our library to Maven local

Let’s now run ./gradlew publishToMavenLocal and see what we end up with:

$ ./gradlew publishToMavenLocal

5 actionable tasks: 5 executed

Navigating to my ~/.m2/repository/com/tomgregory directory I can see we’ve now got a gradle-java-library-plugin-library directory. Within that we have another directory for our 0.0.1-SNAPSHOT version:

Let’s then take a look inside the pom file, which any consumers of this library can use to determine transitive dependencies:

Info: the .pom file is an xml file containing metadata about the artifact, popularised by the Maven build tool. This metadata format is still used, even when using different build tools such as Gradle.

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="" xmlns=""
  <!-- This module was also published with a richer model, Gradle metadata,  -->
  <!-- which should be used instead. Do not delete the following line which  -->
  <!-- is to indicate to Gradle or any Gradle module metadata file consumer  -->
  <!-- that they should prefer consuming it instead. -->
  <!-- do_not_remove: published-with-gradle-metadata -->

You can see here that:

  • jackson-databind is a compile time scoped dependency
  • google-http-client is a runtime scoped dependency

Awesome Gradle, you got it right! ✅ Therefore any consumers of this artifact should honour the scopes of these transitive dependencies.

Consuming our library

The last step here is to consume this library within a Gradle built application and ensure the classpaths are setup according to the scopes defined in the library’s pom file. We’ll create a project called gradle-java-library-plugin-consumer.

The build.gradle file for the consumer should look like this:

plugins {
    id 'java'

group 'com.tomgregory'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {

dependencies {
    implementation group: 'com.tomgregory', name: 'gradle-java-library-plugin-library', version: '0.0.1-SNAPSHOT'
  • we’re applying the Java plugin rather than the Java Library plugin, as this is a plain old Java project
  • we’re adding mavenLocal() and mavenCentral() repositories, so we can pull the library we just published locally as well as its transitive dependencies
  • we have an implementation dependency on our library

And just for fun lets add a Java class DoStuff to the com.tomgregory package, to make sure this all works:

package com.tomgregory;


public class DoStuff {
    public static void main(String[] args) throws IOException {
        AwesomeService awesomeService = new AwesomeService();


Here we’re instantiating the service provided by our library and calling getWebPage. We’re also calling the get method of JsonNode, provided by the jackson-databind artifact available on the compile classpath. This method drills down into the returned JSON object to get the value of the status field.

Let’s run the main method:

16:19:48: Executing task 'DoStuff.main()'...

> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes

> Task :DoStuff.main()

2 actionable tasks: 2 executed
16:19:51: Task execution finished 'DoStuff.main()'.

Inspecting the classpath

Everything seems to be working in our application as expected, but if you’re anything like me you’ll want to see under the covers to verify this. Fortunately Gradle provides the dependencies task which shows us both the compile and runtime classpaths.

To get the compile classpath run ./gradlew dependencies --configuration compileClasspath

compileClasspath - Compile classpath for source set 'main'.
\--- com.tomgregory:gradle-java-library-plugin-library:0.0.1-SNAPSHOT
     \--- com.fasterxml.jackson.core:jackson-databind:2.10.2
          +--- com.fasterxml.jackson.core:jackson-annotations:2.10.2
          \--- com.fasterxml.jackson.core:jackson-core:2.10.2

And for the runtime classpath run ./gradlew dependencies --configuration runtimeClasspath

runtimeClasspath - Runtime classpath of source set 'main'.
\--- com.tomgregory:gradle-java-library-plugin-library:0.0.1-SNAPSHOT
     |    +--- org.apache.httpcomponents:httpclient:4.5.11
     |    |    +--- org.apache.httpcomponents:httpcore:4.4.13
     |    |    +--- commons-logging:commons-logging:1.2
     |    |    \--- commons-codec:commons-codec:1.11
     |    +--- org.apache.httpcomponents:httpcore:4.4.13
     |    +---
     |    +---
     |    |    +---
     |    |    +---
     |    |    +---
     |    |    +--- org.checkerframework:checker-compat-qual:2.5.5
     |    |    +---
     |    |    \---
     |    +---
     |    +--- io.opencensus:opencensus-api:0.24.0
     |    |    \--- io.grpc:grpc-context:1.22.1
     |    \--- io.opencensus:opencensus-contrib-http-util:0.24.0
     |         +--- io.opencensus:opencensus-api:0.24.0 (*)
     |         \--- -> 28.2-android (*)
     \--- com.fasterxml.jackson.core:jackson-databind:2.10.2
          +--- com.fasterxml.jackson.core:jackson-annotations:2.10.2
          \--- com.fasterxml.jackson.core:jackson-core:2.10.2

We can see here that the compile classpath as expected has our library and jackson-databind only. The runtime classpath, on the other hand, has google-http-client and all of its transitive dependencies.

5. Final thoughts

If you’re still with me at this point, hopefully you can see the power of using api and implementation dependencies in your libraries.

It might not make that much difference for a small project, but once you start creating multiple libraries consumed by multiple applications, you’ll benefit from following the best practice described above.

6. Resources

Grab the library code
Get the consumer code

Check out these Gradle docs on the Java Library plugin

If you prefer to learn in video format, check out this accompanying video to this post on the Tom Gregory Tech YouTube channel.

Get going with Gradle course
Gradle icon

Want to learn more about Gradle?
Check out the full selection of Gradle tutorials.

How to use Gradle api vs. implementation dependencies with the Java Library plugin

15 thoughts on “How to use Gradle api vs. implementation dependencies with the Java Library plugin

  1. Hi I am new to gradle and trying to migrate from ant to gradle.
    So basically, in my ivy.xml I have run time dependencies:

    And I am trying to use it in my build.gradle as I am getting essbase errors in my java code:

    please suggest how can I use these run time dependencies in build.gradle.

    1. Hi Chetan. I suggest using the implementation dependency configuration e.g.

      implementation group: '', name: 'google-http-client', version: '1.34.2'

      I’m going to guess you tried this already though, since this is the topic of the article? Please add more details for your specific issue. Thanks!

  2. Hey Tom, great post.
    A bit of a niche, but you may want/be curious to explore current limitations surrounding this when used with Groovy. I’ve just recently run into an issue where I’ve written 2 libraries, and a consuming Groovy “app”. The libraries are also Groovy. We’ll say “app”, and libraries “A” and “B”.
    App consumes library A, library A has a compile-classpath dependency on B (A uses classes that come from B). App calls the encapsulating methods from library A. Pretty straightforward, right?? Well… apparently, there is something in the Groovy plugin or something, that when you run the task `compileTestGroovy`, it loses runtime dependencies that it should have. When trying to compile, the compiler gets into one of library A’s classes, and when it comes across an object initialization of one of the classes from library B, we get a ClassDefNotFound. Here’s where it’s weird…. This is all if you have library A depend on library B with “implementation”. If I move to using “compile” between library A and library B, and just keep “testImplementation” or “testCompile”, it all works. Crazy…

    1. Hi Ryan. Thanks for sharing this scenario. Not something I’ve come across but if you think there might be a issue with Gradle it might be worth raising on their forum.

  3. Nevermind, I just realized I was supposed to run the dependencies task on the consumer, not on the library xD

  4. Hi, thank you for the helpful blog post. I cloned the library project and ran ./gradlew dependencies, but i see both libs listed under compileClasspath: which is different from the instructions in the blog post. Can you please tell me what am I doing wrong?

  5. Hi Tom Gregory,
    Awesome blog with realtime example answered most of my questions related to `ap` and `implementation`.

    I think there is typo in below line.
    `Library C, as this is used internally in Library B` ==> `Library C, as this is used internally in Library A`

    1. Hi Karthik. Glad you’re finding the blog helpful. Thanks so much for highlighting my mistake, which I’ve now corrected.

      Looking forward to your feedback on future posts.


  6. Hi Tom,

    I am new to Gradle and i came across your site while searching for Gradle tutorials.
    I find your tutorials to be very helpful for a new beginner in Gradle.

    i am using Gradle 6.1 and I am writing small application to understand the concepts of multi project Application and Java-Library plugin of Gradle.

    I have some doubts regarding multi-project application and “api” configuration of Java-Library plugin in gradle.

    I have posted my doubt in Stackoverflow and below is the link of my Stackoverflow post.

    Can you please help me to clear my doubts.


    1. Hi K. Verma. Thanks for your comment. I can see that your StackOverflow question has already been answered.

      It’s an interesting point though. It seems that IntelliJ cannot recognise the ‘api’ method when using the approach of configuring subprojects from the parent e.g.

      project(‘:SubProject-2’) {
      apply plugin: ‘java-library’

      dependencies {
      api ‘org.apache.commons:commons-math3:3.2’

      Although we can see that the above code does build in Gradle, IntelliJ cannot recognise ‘api’ and shows it as greyed out in the editor. I’m going to assume this is due to how IntelliJ is managing the application of the ‘java-library’ plugin in this case.

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top


Getting started with Gradle just got A LOT easier!

Get Going with Gradle is the fastest way to a working knowledge of Gradle.

✅ Work effectively in basic Gradle projects
✅ Know how to setup Java projects in Gradle
✅ Understand the Gradle fundamentals

And it's yours today for FREE!

Where shall I send your access details?

Please wait...

Thank you, check your e-mail inbox for all the details!