Declare your private repository with Gradle's exclusiveContent and an includeGroup filter for your internal group names. Then no other repository can supply those artifacts, even one listed earlier. Gradle's docs name security, performance and reliability as the reasons for content filtering, and it takes about ten lines.
Why order alone is not enough
Gradle searches repositories in the order you declare them, and once it finds a module's metadata in one repository it downloads that module's artifacts from the same place. That is a sensible default, but it means a public repository listed first gets the first chance to answer for any coordinate, including one that only exists internally.
If an attacker publishes a public artifact under a group name you use privately, the public repository can answer first. This is the shape of a dependency confusion attack. Reordering helps, but it is a convention someone can break later. A filter is a rule the build enforces.
The three filtering tools
| Tool | What it does | Use it when |
|---|---|---|
| includeGroup on a repository | Limits that repository to the named group | A private repository holds only your own groups |
| exclusiveContent | Declares that a repository is the only source for matching artifacts | You want other repositories blocked from serving those artifacts |
| includeModule or version filters | Narrow the match to a module or version, exact or by regular expression | One public module lives in a special repository |
The Gradle docs describe filters by group, module or version, using exact values or regular expressions.
Example
This is the pattern from the Gradle documentation, shown in the Kotlin DSL:
repositories {
// Not searched for my.company artifacts, despite being declared first
mavenCentral()
exclusiveContent {
forRepository {
maven {
url = uri("https://repo.mycompany.com/maven2")
}
}
filter {
includeGroup("my.company")
}
}
}
Read it as two rules. The public repository is never asked for my.company artifacts. The private repository is the only source for them. If the private repository is down, the build fails loudly instead of quietly accepting a public lookalike.
Steps to adopt it
- List every group name your organization publishes internally.
- Put those names in includeGroup filters on the private repository, in the settings file if you centralize repositories.
- Use exclusiveContent for groups that must never come from anywhere else.
- Do the reverse for special repositories: if a vendor repository serves one group, filter it to that group so it cannot answer for others.
- Run a build with a clean cache and read the resolution output for unexpected sources.
The same logic applies in the other direction. Gradle's docs also point out that filtering avoids leaking which private dependencies you use, because requests for internal groups are never sent to public servers.
What filtering does not do
It does not prove an artifact is genuine. It controls where Gradle looks, not what it finds. Pair it with dependency verification for checksums and locking for repeatable versions. For the wider repository picture on Android, see how to control where Gradle downloads Android dependencies from.
Try it yourself
Click through it at your own pace, or open it full screen.
Click into a component and look at where it came from. Anything with an unexpected source deserves a filter.
Safeguard's dependency scanning and SBOM generation list every resolved component in a repository, which makes unexpected sources easy to spot. You can run the free scan at gold.safeguard.sh/scan.
Quick check
Does a repository declared first always win in Gradle?
Gradle searches in declaration order, so yes for any coordinate the first repository can answer. Content filters change that: a filtered repository is skipped for artifacts it is not allowed to serve.
Is exclusiveContent the same as includeGroup?
No. includeGroup limits what one repository may serve. exclusiveContent also stops all other repositories from serving the matching artifacts.
Sources
Last reviewed: 19 October 2026.