Read settings.gradle to get the list of subprojects, then resolve dependencies for each one by its project path and report results per subproject. A single flat result hides which module pulled in a vulnerable library, and that is the detail the owning team needs.
How Gradle declares subprojects
The Gradle user guide says subprojects are declared in settings.gradle or settings.gradle.kts using include, with project paths as arguments. By default a project path matches the relative physical location of the directory, so a path of services followed by api maps to the services/api folder.
A multi-project build has a root settings file, an optional root build file, and one directory per subproject, each with its own build file. Subprojects can depend on each other with a project dependency, and the required project is built first.
rootProject.name = "my-project"
include("app", "core", "util")
Workspace detection for Gradle
Detection is simple in principle. The include lines in the settings file are the truth. Folders that have a build file but no include line are not part of the build.
| Signal | Meaning |
|---|---|
| Listed in include | A subproject in the build |
| Has build file, not listed | Not built, decide if it is in scope |
| Listed, no folder | Broken path, fix the settings |
Watch for dynamic includes. Some builds compute the list in code, for example by walking directories. A scanner that parses the file as text will not see those, so use a tool that asks Gradle itself.
Per subproject resolution
The guide shows the colon path convention for running tasks, for example a compile task on the api project. The same convention applies to the built in dependencies report. Run it for one subproject at a time:
./gradlew :app:dependencies
Each configuration in the output is a classpath. Your scan should care most about the runtime classpath of deployable subprojects, and less about test or build tooling classpaths. If your tool merges everything into one list, label each finding with the configuration it came from.
Project dependencies are internal
When app depends on core through a project dependency, core's classes and transitive dependencies join app's classpath. Core is your own code. A third party library that core pulls in is a dependency of app too. So fix it once in core and both subprojects improve.
Reporting that helps
- Group findings by subproject path.
- Show the path from the subproject to the vulnerable library, including the internal hop.
- Note whether the library is on a runtime classpath.
That is enough for each team to act without reading the whole build.
If you already pin versions, see the related post on Gradle dependency locking, since locked versions make per subproject results repeatable.
Safeguard scans dependencies and generates SBOMs per project. The free scan at https://gold.safeguard.sh/scan is a quick way to try one subproject.
Try it yourself
In the demo, follow a vulnerable component up to the project that declared it. That is the per subproject view you want from a Gradle scan.
Click through it at your own pace, or open it full screen.
Quick check
How does Gradle know which folders are subprojects?
The include entries in the settings file. A folder with a build file but no include entry is not part of the build.
Should I scan test classpaths too?
Report them, but label the configuration. Runtime classpaths of deployable subprojects usually matter most.
Sources
Last reviewed: 19 October 2026.