Safeguard
Developer Tools•

Gradle api vs implementation: does it change your exposure?

In Gradle, api passes a dependency to consumers while implementation keeps it off their compile classpath. What that means for who inherits a library.

Safeguard Security Team
3 min read

Yes, partly. With api, a dependency appears on your consumers' compile and runtime classpaths. With implementation, it stays off their compile classpath but is still present at runtime. Neither one removes a vulnerable library from the running application. They change who can compile against it.

Definitions

Both configurations come from the Gradle java-library plugin.

ConfigurationConsumer compile classpathConsumer runtime classpath
apiYesYes
implementationNoYes

The Gradle documentation calls the api case transitive exposure: a consumer can use types from those libraries without declaring them. It advises defaulting to implementation and using api only when a library's type appears in your public interface, such as a method parameter, a superclass or a public field.

What this means for security

You publish a library. Everything under api becomes part of your contract. Consumers can start compiling against it, so upgrading or removing it later becomes a breaking change. A vulnerable library under api is harder to replace quietly.

You consume a library. Runtime still includes the library either way. A scanner reading the runtime classpath sees it. A scanner reading only the compile classpath may miss the implementation dependencies of your dependencies.

Applications. A pure application has no consumers, so api versus implementation matters little for exposure. Its main effect is on compile speed and accidental use.

Why the compile classpath is not the whole story

Keeping implementation dependencies off the compile classpath reduces compile classpath size and recompilation, according to the docs. It does nothing about what ships. When you need the shipped set, resolve the runtimeClasspath configuration. See Why is this dependency on my classpath.

Example

A shared module declares api for an HTTP client because its public constructor takes a client type, and implementation for a string utility library. A service that depends on the module can compile against the HTTP client types without declaring them. If a vulnerability is announced in the HTTP client, the service owner may not know the library is there, because their build file never named it. The string library is still on the runtime classpath but invisible on their compile classpath. Both appear in a runtime resolution, which is why that is the view to use for risk.

Why it matters when you fix

Raising a version for a library under api reaches every consumer's compile classpath. A change in a public type can break their build. A library under implementation is easier to change, since only runtime behaviour is involved. Plan the upgrade path with that in mind.

A practical rule set

  1. Default to implementation.
  2. Use api only for types in your public signatures.
  3. Run dependencyInsight against runtimeClasspath, not only compileClasspath.
  4. If a vulnerable library sits under api, plan a deprecation, since consumers may rely on it.
  5. Use constraints to raise versions for consumers, see forcing a patched transitive version.

Quick check

Does implementation hide a library from scanners?

Not from a scanner that reads the runtime classpath or a lockfile. It only hides it from consumers' compile classpath.

When should I use api?

When a type from the library appears in your public methods, superclasses or fields.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.