Contents

Dependency Management in Gradle: API vs Implementation

In Gradle, there are two types of dependencies that you can declare for your project: API dependencies and implementation dependencies. The main difference between these two types of dependencies is how they are exposed to other modules that depend on your project.

API vs Implementation

API dependencies are dependencies that are part of your project’s public API. They are exposed to other modules that depend on your project, which means that they can be used by those modules in their code. API dependencies are typically used for interfaces or abstract classes that define the contract between your project and its clients.

On the other hand, implementation dependencies are dependencies that are used internally by your project and are not part of its public API. They are not exposed to other modules that depend on your project, which means that they cannot be used by those modules in their code. Implementation dependencies are typically used for concrete classes or libraries that your project uses to implement its functionality.

To declare an API dependency in Gradle, you use the api configuration. For example:

dependencies {
    api 'com.example:mylibrary:1.0.0'
}

To declare an implementation dependency, you use the implementation configuration. For example:

dependencies {
    implementation 'com.example:mylibrary:1.0.0'
}

By default, if you do not specify a configuration, Gradle assumes that you are declaring an implementation dependency.

Example

Let’s create a project with 3 modules:

.
├── internal-dep
├── api-dep
├── service
└── settings.gradle

With the following build.gradle files and classes:

  1. internal-dep:
// build.gradle
plugins {
    id 'java-library'
}

group 'dev.batalin.intdep'
version '1.0-SNAPSHOT'
// dev.batalin.intdep.IntLibTool
package dev.batalin.intdep;

public class IntLibTool {
    public static void print() {
        System.out.println("IntLibTool");
    }
}
  1. api-dep:
// build.gradle
plugins {
    id 'java-library'
}

group 'dev.batalin.apidep'
version '1.0-SNAPSHOT'

dependencies {
    implementation project(':internal-dep') // NB: implementation dependency
}
// dev.batalin.apidep.ApiLibTool
package dev.batalin.apidep;

import dev.batalin.intdep.IntLibTool;

public class ApiLibTool {

    public static void print() {
        IntLibTool.print(); // NB: It's okay to use int-lib here
        System.out.println("ApiLibTool");
    }
}
  1. service:
// build.gradle
plugins {
    id 'java'
}

group 'dev.batalin.service'
version '1.0-SNAPSHOT'

dependencies {
    implementation project(':api-dep') // NB: specify api-dep only
}
// deb.batalin.service.Main
package dev.batalin.service;

import dev.batalin.apidep.ApiLibTool;

public class Main {
    public static void main(String[] args) {
        ApiLibTool.print(); // NB: It's fine. You can use api-dep here
        IntLibTool.print(); // Error: you can't use internal-dep here, because it's an api-dependecy in the api-dep module
    }
}

However, you can start using IntLibTool in the service module, if you change the dependency type in api-dep/build.gradle with:

dependencies {
    api project(':internal-dep')
}

Conclusion

So that’s it! Now you know the difference between API and implementation dependencies in Gradle. By using these dependencies correctly, you can create a clear separation between the public interface and the internal implementation of your project. This can make it easier for other developers to use your code, and it can also help you avoid potential issues with dependencies.