Follow a public type
Suppose module :networking exposes a method returning a third-party HTTP client's response type. Consumers compiling against that method need to understand the exposed type. That suggests an api relationship—or a redesign that returns your own stable model instead.
If the HTTP client is used only inside private implementation code, implementation keeps that detail out of the consumer-facing compile contract.
The Gradle Java library documentation explains API separation, while Android's dependency guide describes its use in Android modules.
Compare the design consequences
| Choice | Review question |
|---|---|
Expose third-party type with api |
Are consumers intentionally coupled to this dependency? |
Hide type with implementation |
Does the public API remain usable without the internal library? |
| Add dependency directly to consumer | Does the consumer independently use that library? |
| Wrap the type in your own model | Is the extra abstraction justified by stability needs? |
A compilation error after changing api to implementation can reveal an existing coupling rather than a Gradle bug.
Do not confuse visibility with packaging
implementation does not mean the dependency is excluded from runtime packaging. It controls dependency exposure and classpaths under the applied plugin model. Inspect the release runtime graph and resulting package for size or distribution questions.
Similarly, compileOnly is not a smaller equivalent of implementation; the required runtime implementation must be supplied through an appropriate mechanism.
Make changes with consumer tests
Build every module that consumes the library, run API-facing tests and exercise the release variant. A library compiling in isolation does not prove that downstream modules still compile or run correctly.
Inspect declarations with APKLint
The Gradle Dependency Checker can highlight scope choices and duplicate declarations. It cannot infer your intended public API from a build snippet alone. Pair its findings with source-level review of exposed types and the build-generated dependency graph.
Use a consumer compilation to expose the boundary
Imagine a library called :catalog returns VendorPage from its public loadPage() method. An application calling that method may need the vendor type while compiling, even if application source never explicitly imports it. In this design the vendor library forms part of catalog's public contract. Compare that with a catalog method returning a project-owned ProductPage whose fields are ordinary strings and numbers. The vendor client can then remain an internal implementation detail.
The Gradle Java library model describes this compile-classpath separation. Android library plugins provide corresponding dependency configurations, but the decision still comes from the actual public types. Names such as client, helper or internal in a dependency alias do not establish whether its classes escape through public signatures.
Migrate one exposed type deliberately
For the second design, introduce the project-owned model, convert the vendor response inside catalog and update callers to use the new contract. Only then change the vendor declaration to implementation. Build a small consumer that depends on catalog without independently declaring the vendor. This is a useful test because a large application may already bring in the vendor through another feature, accidentally concealing the public dependency problem.
Exercise error handling too. A public method that returns a project model but throws a documented vendor-specific exception can still couple consumers to the vendor. Decide whether to translate that exception into an application error type or intentionally retain the dependency in the public interface. Apply the same reasoning to generic parameters, supertypes and annotations that consumers must understand.
When publishing the library, test a consumer using the published artifact and metadata in addition to an in-repository project dependency. That checks the contract users actually receive. Keep the old API if compatibility requires it, with a planned migration rather than an abrupt scope change. The goal is a clear and usable interface; reducing visible dependencies is beneficial only when consumers retain the types and behavior their supported integration needs.
Sources and further reading
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.


