Bind the CycloneDX Maven plugin's makeAggregateBom goal at the root of the build. It writes one aggregate SBOM covering every module, and can also emit one per module. Check the scope defaults first, because test scope is excluded unless you turn it on.
Definitions
An SBOM is a machine readable list of the components in a piece of software. CycloneDX is one standard format for it. In a multi-module Maven build, you can describe each module separately, or the whole product at once.
The goals
| Goal | Produces |
|---|---|
| makeBom | A BOM for each module |
| makeAggregateBom | An aggregate BOM at the build root, optionally with each module's BOM |
| makePackageBom | A BOM for each module with war or ear packaging |
Any goal can be skipped with cyclonedx.skip=true.
Configuration sketch
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>makeAggregateBom</goal>
</goals>
</execution>
</executions>
</plugin>
Pin the plugin version your team has approved. The plugin's own page binds the aggregate goal to package in its usage example.
Defaults worth knowing
- Output format defaults to all, so you get XML and JSON, named bom by default in the build directory.
- The schema version is 1.7 in current releases. The plugin version decides it, with older plugin lines producing older schemas.
- Compile, provided, runtime and system scopes are included. Test scope is not, since includeTestScope is false.
- Attached BOMs get the cyclonedx classifier during install or deploy. Set cyclonedx.skipAttach=true to stop that.
- You can exclude modules, for example with excludeArtifactId or excludeTestProject.
Which one should you ship?
For a service, ship the aggregate or the BOM for the deployable module. For a library, ship each published module's BOM, since consumers get modules, not your whole reactor. Match the SBOM to what is actually delivered.
Common pitfalls
- Generating the SBOM in a pipeline stage that runs before dependencies are resolved for every module.
- Forgetting that excluded modules, such as test projects, will not appear.
- Shipping a root SBOM for a library whose consumers only receive one module.
- Leaving the generated file in the build directory and never storing it. An SBOM nobody can find during an incident does not help.
Check the result
- Open the JSON and count components.
- Confirm a few known dependencies are present.
- Confirm each component has a package URL, see how to write a purl.
- Store it with the build artifact.
- Remember that a shaded jar needs extra attention, see shaded JARs.
Try it yourself
Click through it at your own pace, or open it full screen.
Look at the component list and its versions. That is the same shape of data an SBOM carries.
Where Safeguard fits
Safeguard ingests and analyses SBOMs and scans dependencies directly. The free scan is a quick comparison against your generated file.
Quick check
Is test scope in the SBOM?
Not by default. IncludeTestScope is false.
Where does the file end up?
By default in the build directory as bom.xml and bom.json, and attached as an additional artifact with the cyclonedx classifier on install or deploy.
Sources
Last reviewed: 19 October 2026.