skip to content

You need internal libraries published to a private repository but releases also mirrored to a public one. How do you model multiple publish repositories and control which build targets which?

level: seniorimportance: should knowfreq 30%

answer

  1. several named maven { } targets
  2. one task set per repository
  3. avoid umbrella publish; call …To<Name>Repository
  4. pipeline decides which task
  5. convention plugin for org consistency

basics

~10 s

Declare multiple named maven { } repositories in publishing.repositories. Each gets its own publishAllPublicationsTo<Name>Repository task, so CI invokes the one(s) appropriate for that build instead of the catch-all publish.

solid answer

~40 s

Because `publishing.repositories` is a `RepositoryHandler`, you can declare several named targets, and Gradle generates an independent task set per repository. That gives you per-destination control: ```kotlin publishing { repositories { maven { name = "internal"; url = uri("https://nexus.corp/internal") } maven { name = "public"; url = uri("https://repo.example.com/releases") } } } ``` Now CI runs `publishAllPublicationsToInternalRepository` for every build and adds `publishAllPublicationsToPublicRepository` only on tagged releases — rather than the umbrella `publish` task, which would push to *all* declared repositories at once. The decision lives in the pipeline (which task it calls), keeping the build script declarative. For consistency across many projects, factor the repository declarations into a shared convention plugin so every module exposes the same named targets and the same CI command works everywhere.

code

kotlin · 10 lines
kotlin
publishing {
  repositories {
    maven { name = "internal"; url = uri("https://nexus.corp/internal") }
    if (providers.gradleProperty("publishPublic").isPresent) {
      maven { name = "public"; url = uri("https://repo.example.com/releases") }
    }
  }
}
// CI internal:  ./gradlew publishAllPublicationsToInternalRepository
// CI release:   ./gradlew publishAllPublicationsToPublicRepository -PpublishPublic

go deeper

for a junior

Know you can declare more than one repository, each with a name.

for a middle

Map each named repository to its own generated task set and invoke the specific one.

for a senior

Design the build-declares / pipeline-selects split, avoid the umbrella publish, and gate optional repos with providers.

for a principal

Own the convention plugin and org-wide naming standard so a uniform CI publish command works across the whole estate and public exposure is governed centrally.

## Multiple named repositories `publishing.repositories` accepts any number of `maven { }` entries. Each declared repository, by virtue of its `name`, gets its own generated tasks: - `publishAllPublicationsToInternalRepository` - `publishAllPublicationsToPublicRepository` - and per-publication variants for each. The global `publish` task depends on **all** of them — which is exactly what you usually do *not* want when destinations differ by build type. ## Controlling which build targets which The cleanest separation is to keep the build script declarative (declare all repositories) and let the **pipeline choose the task**: - Every CI build: `./gradlew publishAllPublicationsToInternalRepository` - Release builds only (e.g. on a git tag): also `./gradlew publishAllPublicationsToPublicRepository` This avoids embedding branch/tag logic in the build script. If you must encode it in Gradle, gate it on a property: ```kotlin repositories { maven { name = "internal"; url = uri("https://nexus.corp/internal") } if (providers.gradleProperty("publishPublic").isPresent) { maven { name = "public"; url = uri("https://repo.example.com/releases") } } } ``` Using `providers.gradleProperty(...)` keeps it configuration-cache friendly versus reading `project.hasProperty` eagerly. ## Consistency at scale With dozens of modules, repeating the repository declarations invites drift (typos in URLs, inconsistent names → CI commands that work in one repo but not another). Extract them into a **convention plugin** (`build-logic`/`buildSrc`) that applies `maven-publish` and registers the canonical named repositories. Then a single org-wide CI command — keyed on the stable names — works across every project. ## Pitfalls - Running `publish` (umbrella) accidentally pushes to *every* repository including public — scope CI to explicit `…To<Name>Repository` tasks. - Two repositories with the same effective name collide; ensure names are unique. - Naming differences across projects break a uniform pipeline; standardise via the convention plugin.

  • Why avoid the umbrella `publish` task in this setup?
    `publish` depends on every declared repository's upload task, so it would push to the public repo on every build. Scope CI to the explicit `…To<Name>Repository` task instead.
  • How do you keep repository declarations consistent across many modules?
    Factor them into a convention plugin in buildSrc/build-logic that applies maven-publish and registers the canonical named repositories, so all modules share identical names and URLs and one CI command works everywhere.
  • How do you conditionally add a repository in a configuration-cache-friendly way?
    Gate it on `providers.gradleProperty("...")` rather than eager `project.hasProperty`, so the read participates in the provider/lazy model the configuration cache expects.

saying these in an interview costs you the question

  • Using the umbrella `publish` task and accidentally pushing internal snapshots to the public repo.
  • Hardcoding branch/tag logic inside the build script instead of selecting the task in the pipeline.
  • Duplicating repository declarations per module and letting names/URLs drift.

context