Safeguard
Software Supply Chain•

How do you generate an SBOM for a Kotlin project?

For Gradle-based Kotlin builds, the CycloneDX Gradle plugin writes a JSON or XML SBOM with one task. Setup, outputs, multi-module notes and checks to run.

Safeguard Security Team
4 min read

For a Gradle-based Kotlin project, add the CycloneDX Gradle plugin and run the cyclonedxBom task. It writes a CycloneDX software bill of materials in JSON and XML under the build reports folder. Run it in CI on every release build and store the result with the artifact.

What does the plugin need?

According to the plugin's README, version 3.x requires Gradle 8.4 or newer and a build JVM of Java 8 or newer, though support for versions before Java 17 is deprecated. It emits CycloneDX schema 1.6 by default, and 1.7 can be selected through the schemaVersion setting. Output formats are JSON and XML. The README says it is tested on Java 8, 11, 17, 21 and 25 and supports configuration cache, parallel execution and the build cache. Check the README for the release you install, since the page documents the current branch.

How do you add it?

Apply the plugin to the root project. In a Kotlin build script the plugins block takes the id org.cyclonedx.bom and a version. The README's quick start shows version 3.5.1 on its current branch, but pick the latest release you have checked. Then generate the aggregate SBOM by running the Gradle wrapper with the cyclonedxBom task.

With default settings, outputs land under build/reports/cyclonedx as bom.json and bom.xml for the aggregate, and under build/reports/cyclonedx-direct for each project's own "direct" SBOM.

Direct versus aggregate SBOMs in multi-module builds

Many Kotlin projects have several modules. The README explains two tasks. cyclonedxDirectBom produces an SBOM for one project and is registered on the project and its subprojects. cyclonedxBom produces one aggregate that combines the enabled direct SBOMs, and is registered only on the project where you applied the plugin, so applying it to the root makes the aggregate cover the root and its contributing subprojects. If an expected direct SBOM is missing, aggregation fails instead of silently producing an incomplete file. Configuration belongs to the tasks directly, and values are not copied between them, so set the schema version on both if you want them to match.

What does an SBOM leave out?

An SBOM lists what the build resolved: libraries from Maven repositories, Gradle plugins you configure, and their transitive dependencies. It does not prove those libraries are safe. Things to check by hand:

  • Android modules: confirm that the variants you ship are the ones described.
  • Kotlin Multiplatform targets: dependencies differ per target, so make sure the target you release is covered.
  • Native or vendored code copied into the repository has no package coordinates and will not appear.
  • Docker images wrapping the application need their own SBOM for the operating system layer.

Constructed example: a service has modules api, core and jobs. The team applies the plugin only in jobs' build file and runs cyclonedxBom there, and the aggregate lists only that module. Applying it at the root fixes the gap.

How to check or apply this

  1. Apply the plugin at the root project and run cyclonedxBom locally.
  2. Open bom.json and confirm that each module's main libraries appear.
  3. Compare the component count with what dependencies output shows for one module.
  4. Set schemaVersion on both tasks if you need to match a customer's required version.
  5. Upload the files as CI build artifacts and attach them to releases.
  6. Scan the SBOM for known vulnerabilities on each build and fail on your policy.
  7. Regenerate on every release, not once a quarter.

Safeguard can ingest SBOMs, scan dependencies and show which vulnerable libraries are reachable. The Safeguard CLI can also generate SBOMs for many package managers. See /demos/sca-dependency-inventory.

Quick check

Which task creates the combined SBOM?

cyclonedxBom, applied at the root project. cyclonedxDirectBom creates one SBOM per project.

Why did the SBOM miss a module?

Likely the plugin or task covered only one project. Aggregation fails if an enabled direct SBOM is missing, so check task enablement and where the plugin is applied.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.