Gradle & Code Quality

Gradle Dependency Types: implementation, api and Runtime Configurations

Gradle dependency configurations answer two questions: where a dependency is available and whether it becomes part of a consumer's contract. In an Android project, choosing implementation, api or a test configuration affects compilation, packaging and module boundaries.

By , FounderUpdated 3 min read

Read the configuration as a scope

Configuration Typical purpose
implementation Internal compile/runtime dependency of the module
api Dependency exposed through a library's public API
compileOnly Needed to compile but not supplied as a normal runtime dependency
runtimeOnly Needed at runtime without being part of the source compile classpath
testImplementation Local test dependency
androidTestImplementation Instrumented Android test dependency

Exact availability depends on the applied plugins and variant model. Android's dependency guide and Gradle's Java library model explain the distinction.

Choose from the code boundary

If a library's public method returns a type from another library, consumers may need that type on their compile classpath. That is a reason to examine api. If the dependency is hidden inside the implementation, exposing it unnecessarily couples consumers to an internal choice.

Do not change every dependency to api merely to fix an unresolved import in a downstream module. Decide whether the downstream module should declare its own dependency or whether the type truly belongs to the upstream public contract.

Keep tests out of production

A mocking or test assertion library normally belongs in a test configuration. Putting it in implementation can add unnecessary code or dependencies to the release graph. Inspect the resolved release configuration rather than assuming a dependency is absent because it is only used by tests.

Understand declarations versus resolution

A declaration is an input to dependency resolution. Transitive requirements, platforms and constraints can select a version different from the one you wrote. Verify the relevant runtime classpath before making size or vulnerability claims.

Review with APKLint

The Gradle Dependency Checker can help inspect declarations and obvious issues. It does not run your complete project graph, so it cannot prove which artifact Gradle selected for every variant. Use it alongside a build-generated dependency report and a clear explanation for each public or runtime dependency.

Map a dependency to the code that needs it

Take an Android module with production code under src/main, local JVM tests under src/test and device tests under src/androidTest. A test assertion library used only by local tests belongs in testImplementation. A UI test library that needs the Android device environment belongs in the instrumented-test configuration. Adding either to the production scope just because an IDE import is unresolved can make the release graph unnecessarily broad.

For a failed import, inspect the file's source set first. A helper placed accidentally under main cannot be repaired cleanly by exposing test libraries to production. Move test-only code to its intended source set, or redesign a shared helper so its production portion has no test-framework dependency. The Android dependency configuration guide defines these scopes in the Android build model.

Make a compile-only runtime contract explicit

Suppose a plugin compiles against an interface supplied by a host application. A compile-only declaration may describe that relationship if the host really provides a compatible implementation when the plugin runs. Write down the supported host contract and test inside that environment. A normal standalone Android application cannot assume an arbitrary third-party library appears on a user's device merely because compilation succeeded.

A runtime-only dependency answers a different need: code can be selected or loaded at runtime without its classes being imported directly by the module's source. Confirm the relevant plugin supports the configuration and that the runtime mechanism can discover the component. Do not use it to hide an unresolved source reference; a compiler still needs every type referenced by source code on an appropriate compile classpath.

Review the release and test graphs separately after moving scopes. The intended result is that production retains required runtime components while test-only libraries remain confined to tests. Run the corresponding tests and an actual release feature path. If a library exposes a third-party type publicly, also review api versus implementation as a consumer contract rather than treating the configurations as interchangeable names for downloading the same file.

Sources and further reading

  1. Gradle: Declaring dependencies
  2. Gradle: Java Library plugin
  3. Android Developers: Add build dependencies

Reference review: 29 September 2026. Examples illustrate the workflow; check your installed versions, release artifact and account-specific Console requirements before applying them. This guide is not a claim that APKLint executed your project or verified your private account.

APKLint

Android inspection tools and practical release guides. About APKLint · Report a correction