A Maven SBOM is a machine-readable inventory of every direct and transitive dependency your Java build pulls in, and the cleanest way to produce one is the CycloneDX Maven plugin, which attaches a CycloneDX bill of materials as a build artifact. Once you have that file, you can feed it to vulnerability scanners, satisfy compliance mandates, and answer "are we affected?" in minutes the next time a Log4Shell-scale advisory lands. This guide shows you how to generate a Maven SBOM and actually use it.
Why you need an SBOM in the first place
A modern Maven project rarely declares more than a fraction of what it ships. Your pom.xml might name a dozen dependencies, but the resolved tree can run to hundreds once transitive dependencies are pulled in. When a critical vulnerability drops in some deeply nested library, the first question, "do we even use this?", is surprisingly hard to answer without an inventory.
That is what a software bill of materials solves. It is a complete, exportable list of components and versions, produced at build time from the same dependency resolution your build already performs, so it reflects what you actually ship rather than what you think you declared.
Regulatory pressure has made SBOMs table stakes too. US Executive Order 14028 and the resulting NTIA minimum-elements guidance pushed SBOMs into procurement requirements, and frameworks across finance and critical infrastructure now expect them.
The CycloneDX Maven plugin
CycloneDX is a full-stack SBOM standard built for application security and supply chain analysis, and its Maven plugin generates a BOM containing the aggregate of all direct and transitive dependencies of a project.
Add it to your pom.xml:
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<configuration>
<projectType>library</projectType>
<schemaVersion>1.6</schemaVersion>
<outputFormat>all</outputFormat>
</configuration>
</plugin>
By default the BOM is attached as an additional artifact with a cyclonedx classifier and an xml or json extension during a Maven install or deploy, so it flows naturally into your existing publishing pipeline.
Choosing the right goal
The plugin exposes three goals, and picking the right one matters for multi-module builds:
makeBomcreates a BOM for each Maven module with its own dependencies.makeAggregateBomcreates a single aggregate BOM at the build root that captures dependencies from the whole multi-module build, and optionally a BOM per module.makePackageBomcreates a BOM for each module withwarorearpackaging.
For a single-artifact service, makeBom is fine. For a multi-module repository where you want one authoritative inventory of the whole application, makeAggregateBom is what you want, because a per-module-only view makes it easy to miss a vulnerable library that lives in a sibling module.
To generate on demand:
# Single project
mvn cyclonedx:makeBom
# Multi-module: one aggregate BOM at the root
mvn cyclonedx:makeAggregateBom
Wiring it into the build lifecycle
Running the goal manually is fine for a one-off, but the value comes from generating the SBOM on every build automatically. Bind the goal to a lifecycle phase:
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>makeAggregateBom</goal>
</goals>
</execution>
</executions>
</plugin>
Now every mvn package produces a fresh, accurate SBOM alongside your JAR, and it stays in lockstep with your dependencies rather than drifting into staleness.
You can also trim noise. The plugin lets you exclude test-scoped projects and filter by artifactId or groupId using comma-separated lists, which is handy when internal build tooling would otherwise clutter the inventory.
XML or JSON, and which schema version
Setting outputFormat to all gives you both XML and JSON. JSON tends to be easier for downstream tooling and scripting; XML is common in older enterprise integrations. There is little cost to emitting both. For schemaVersion, pin a recent CycloneDX version such as 1.6 so the fields your scanners expect are present, and revisit it as the spec advances.
Putting the SBOM to work
Generating the file is only half the job. An SBOM that sits unread in a build artifact is compliance theater. The point is to consume it.
The immediate use is vulnerability scanning. Feed the CycloneDX file to a scanner that maps components to known advisories, and you get a prioritized list of vulnerable dependencies with the versions to upgrade to. Because the SBOM already enumerates transitive dependencies, you catch the deep ones that a glance at pom.xml would never surface. An SCA tool such as Safeguard can ingest a CycloneDX SBOM and flag vulnerable and end-of-life components transitively, then track them over time.
The second use is incident response. When the next widespread advisory hits, you query your stored SBOMs across services and immediately know which builds contain the affected component and at what version, instead of manually grepping dozens of repositories under pressure. For the Java-specific angle on that, our Jackson databind security guide shows how transitive risk hides in common libraries.
The third use is provenance and compliance. A retained, timestamped SBOM per release is evidence for auditors and a contractual deliverable for customers who now demand one.
FAQ
What does a Maven SBOM actually contain?
A complete list of components, including every direct and transitive dependency with its version and identifiers, produced from your build's resolved dependency tree. The CycloneDX format also carries metadata like licenses and package URLs.
Which plugin goal should I use for a multi-module project?
Use makeAggregateBom to produce one authoritative BOM at the build root covering the whole build. Per-module-only BOMs make it easy to overlook a vulnerable library in a sibling module.
Should I generate XML or JSON?
Set outputFormat to all to emit both. JSON is generally easier for downstream automation, while XML is common in legacy enterprise integrations.
How do I keep the SBOM from going stale?
Bind the CycloneDX goal to a build phase such as package so a fresh SBOM is generated on every build, keeping it in sync with your actual dependencies.