api vs implementation

Two ways to declare a module dependency

Splitting into modules only pays off if changing one module doesn't force every other module to recompile too. That's what Gradle's two dependency configurations control:

  • implementation(project(...)) β€” the dependency is used internally but hidden from anything that depends on you. If :feature:bookings uses implementation(project(":core:network")), then :app (which depends on :feature:bookings) has no idea :core:network even exists β€” and can't accidentally reach into it.
  • api(project(...)) β€” the dependency leaks through. Anything that depends on you can also see and use it directly, transitively.

Default to implementation. It gives Gradle a smaller, more precise picture of what actually needs rebuilding when something changes β€” change :core:network and only the modules that implementation-depend on it (plus anything whose public API changed) need to recompile, not every module transitively downstream. Reach for api only when a type from that dependency appears in your own module's public function signatures β€” otherwise everyone downstream pays for a dependency they never asked for.