Run the aggregate task when you need one SBOM for the whole Gradle build, and the direct task when each project ships on its own. In the CycloneDX Gradle plugin 3.x these are cyclonedxBom and cyclonedxDirectBom. The aggregate is the recommended starting point.
The two tasks in plain terms
A direct SBOM describes one Gradle project and its dependencies. An aggregate SBOM is composed from the direct SBOMs of the projects that contribute to it. That composition is why running the aggregate task also generates the direct files it consumes.
| Task | Use it when | Default output |
|---|---|---|
| cyclonedxBom | You need one SBOM for the build | build/reports/cyclonedx/bom with json and xml |
| cyclonedxDirectBom | You need an SBOM per project, or for one project | each project's build/reports/cyclonedx-direct/bom |
Where you apply the plugin decides the scope
In plugin 3.x, applying the plugin to a project registers the direct task on that project and its subprojects. It registers the aggregate task only on the project where you applied it. Apply it on the root project and the aggregate covers the root plus every contributing subproject. Apply it on a single subproject and the aggregate covers that subtree.
This is the usual source of confusion. A team applies the plugin in a leaf module, runs the aggregate task, and wonders why other modules are missing.
A decision guide
Ask what the SBOM is attached to.
- One deployable built from the whole repository, such as a single service with several internal modules: use the aggregate.
- Several independently shipped artifacts in one repository, such as a library and a command line tool: use the direct task per project, and attach each file to the artifact it describes.
- A mix: generate both, and label them so nobody confuses a repository-wide file with a per-artifact one.
The README makes a related point about matching an SBOM to what it describes: an artifact should be paired with an SBOM whose boundary covers it. A jar from one project normally pairs with that project's direct SBOM. Use the aggregate only when the distribution really represents the same set of projects.
The commands
Aggregate for the whole build:
./gradlew cyclonedxBom
All direct SBOMs without the aggregate:
./gradlew cyclonedxDirectBom
One subproject:
./gradlew :subproject:cyclonedxDirectBom
A failure mode worth knowing
The aggregate is built from direct SBOMs of projects whose direct tasks are enabled. If an expected direct SBOM is missing, aggregation fails instead of producing an incomplete document. That is a good default, because a quietly partial SBOM is the worse outcome.
To keep a project out of the aggregate, disable its direct task with enabled set to false in that project's build script. The README warns against using an execution time condition such as onlyIf for this. A task skipped only at execution time is still an expected producer, so the aggregate fails when its output is missing.
Try it yourself
Click through it at your own pace, or open it full screen. Notice how one inventory can be viewed per project or rolled up, which is the same choice you make with these two tasks.
Safeguard builds an SBOM and dependency inventory from your repositories, and the free scan is a way to see a Gradle project's dependencies without changing your build.
Quick check
Does the aggregate task also create per-project files?
Yes. It generates the direct SBOMs it consumes, so you get both with the default configuration.
Why is a module missing from my aggregate?
Most likely the plugin was applied below the root project, or that module's direct task is disabled. Check where the plugin is applied first.
Sources
Last reviewed: 19 October 2026.