CRA Desk
Every generate step

Generating an SBOM for Java / Kotlin (Maven)

The step below writes a CycloneDX JSON software bill of materials (SBOM) in your own pipeline. The CRA Desk scan reads that file and turns it into a Cyber Resilience Act (CRA) readiness report - it never generates one for you.

Add to pom.xml:

<plugin>
  <groupId>org.cyclonedx</groupId>
  <artifactId>cyclonedx-maven-plugin</artifactId>
  <version>2.9.1</version>
  <configuration>
    <outputFormat>json</outputFormat>
  </configuration>
</plugin>

Run in CI:

mvn -B org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -DoutputFormat=json
Produces:
target/bom.json
Format:
CycloneDX JSON
Generator documentation

What the generator actually reads

The plugin runs inside the build and reads the graph Maven itself resolved, after dependencyManagement and dependency mediation have had their say. What lands in the SBOM is therefore what would land on the classpath, not what pom.xml declares - a difference that shows up wherever a managed version overrides a transitive one.

Versions and package URLs

Components arrive as pkg:maven/groupId/artifactId@version, the identifier OSV uses for its Maven advisories. Scopes are what to watch: test and provided dependencies are part of the resolved graph, so unless the build excludes them, advisories against a test framework end up in a report about a product that never ships it.

The whole CI job

GitHub Actions

- uses: actions/checkout@v4
- run: mvn -B org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -DoutputFormat=json
- uses: mmalinowski/cradesk-action@v1
  with:
    sbom-path: target/bom.json

GitLab CI/CD

generate-sbom:
  script:
    - mvn -B org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -DoutputFormat=json

include:
  - component: gitlab.com/cradesk/scan/readiness@1
    inputs:
      sbom_path: target/bom.json

Only the first step changes with your stack - the CRA Desk step after it is identical everywhere.

What goes wrong here

makeAggregateBom is the important word: a multi-module build without it writes one bom per module and the scan reads a single file rather than merging them.

Repositories with more than one thing in them

Profiles change the answer. A dependency that activates only under -Pprod is absent from an SBOM generated without it, so generate against the same profiles the release build uses - and take the file from the aggregating module's target/, not from whichever module happened to run last.

What CRA Desk does with the result

Components from this build carry pkg:maven package URLs, which the watch matches against OSV's Maven advisories when the SBOM arrives and again as new advisories land. Matching runs on every plan; e-mail alerts and the full match list are what a paid plan adds.

A generator that runs is not the same as coverage. Components with no purl, no version, or from an ecosystem outside the ones we watch are counted as gaps in every report rather than reported as clean.

Other stacks