Shipmark / Guides

How to generate an SBOM for an Android app

Updated 15 November 2026 · 6 min read

In short

  • An Android app resolves its libraries through Gradle. A version catalog (gradle/libs.versions.toml) lists the versions you request, but Gradle's documentation says catalogs do not enforce them. The versions that actually ship are the resolved ones.
  • Gradle dependency locking records the resolved versions in gradle.lockfile. That file can be turned into a CycloneDX SBOM, and the Gradle documentation says lock files should be checked in to source control.
  • Locking is off by default, and the build script's own plugins have a separate lock file. Switch it on, commit the files, then generate one SBOM per release.

What the CRA asks for

Annex I, Part II, point (1) of Regulation (EU) 2024/2847 requires manufacturers to "identify and document vulnerabilities and components contained in products with digital elements, including by drawing up a software bill of materials in a commonly used and machine-readable format covering at the very least the top-level dependencies of the products". The text names no format. CycloneDX and SPDX are the usual choices, and this guide uses CycloneDX.

For an Android app, "components" are the libraries Gradle pulls into the build: AndroidX, Kotlin libraries, networking, analytics and ad SDKs, and their own dependencies. They come from Maven repositories. Anything you copy into the project by hand, such as an .aar or .jar file, is not covered by a lockfile.

Step 1. Know what a version catalog does and does not do

Most current Android projects keep dependency versions in a Gradle version catalog. By default it is the file gradle/libs.versions.toml, which Gradle imports automatically. It has four sections: [versions], [libraries], [bundles] and [plugins].

[versions]
groovy = "3.0.9"

[libraries]
groovy-core = { group = "org.codehaus.groovy", name = "groovy", version.ref = "groovy" }

That example is from Gradle's documentation. The catalog is a good place to read your direct dependencies, but it is not an SBOM. Gradle's documentation states that "version catalogs declare requested versions but do not enforce them", and that Gradle may still select different versions because of conflicts in the dependency graph. Transitive dependencies are not in the catalog at all. For the versions that really end up in the app you need the resolved graph, and dependency locking is the built-in way to record it.

Step 2. Turn on Gradle dependency locking

According to the Gradle user guide, locking is enabled per dependency configuration. To lock all project configurations, use lockAllConfigurations() in the dependencyLocking block:

// build.gradle.kts
dependencyLocking {
    lockAllConfigurations()
}

Then write the lock state:

./gradlew dependencies --write-locks

The documentation says this creates or updates the lock state for each resolved configuration during that build, and that Gradle does not write it if the build fails. What you get:

FileWhat it covers
gradle.lockfileLocated at the root of each project or subproject directory. Each line is group:artifact:version followed by the configurations that include it.
buildscript-gradle.lockfileThe build script's own classpath (for example, Gradle plugins). lockAllConfigurations() covers project configurations but not the buildscript ones, so these need activateDependencyLocking() inside buildscript.

Three practical points from the same page:

We did not test locking on Android project variants. Android builds have many configurations (one set per build type and flavor), so run the command on your real project and open the result. Confirm that your release variant's runtime dependencies are in it.

Step 3. Turn the lock file into CycloneDX

Option A: in your browser

Drop gradle.lockfile on the Shipmark free scanner. It builds a CycloneDX 1.6 SBOM locally and checks each version against OSV.dev. The file stays in your browser, and only package names and versions are sent to OSV.dev.

Option B: the CycloneDX Gradle plugin

The CycloneDX Gradle plugin (plugin id org.cyclonedx.bom, version 3.6.1 on the Gradle plugin portal when we read it) is described there as creating "an aggregate of all direct and transitive dependencies of a project" and generating a CycloneDX document from it. Apply it in the plugins block:

plugins {
  id("org.cyclonedx.bom") version "3.6.1"
}

Release notes for version 3.0.0 describe a task cyclonedxDirectBom for a single project, with cyclonedxBom aggregating subprojects, and output under build/reports/cyclonedx-direct/ and build/reports/cyclonedx/. Those notes are not fully consistent about the aggregate task name, and older tutorials show build/reports/bom.json. Read the plugin's current README for the version you install. The release notes do not mention Android, so test it on your app module.

Option C: Syft or another scanner

Check the documentation of the scanner you prefer for Gradle lockfile support and CycloneDX output. We did not verify any other tool for this guide.

Whichever route you use, open the result. Confirm that the library names, exact versions and package URLs (pkg:maven/…) look right and that nothing you ship is missing. The free SBOM checker compares a CycloneDX or SPDX file with the CRA minimum.

For known-vulnerability scanning, Google's OSV-Scanner documentation lists gradle.lockfile and buildscript-gradle.lockfile among the lockfiles it supports under the Java ecosystem. Check the current page before you build a pipeline on it.

Step 4. Close the gaps lockfiles leave

These are our practical suggestions, not CRA text.

Step 5. Automate it in CI

The free open-source Shipmark SBOM action reads lockfiles on every build or release, writes one CycloneDX 1.6 SBOM and checks it against OSV.dev:

- uses: actions/checkout@v4
- uses: shipmark-eu/sbom-action@v1
  with:
    output: sbom-${{ github.ref_name }}.cdx.json

Common questions

Does my Android app need an SBOM under the CRA?

If you place the app on the EU market as part of a commercial activity, Annex I, Part II, point (1) applies to you as manufacturer. See whether the CRA applies to mobile apps. The full set of requirements applies from 11 December 2027.

Does a Gradle version catalog pin my dependency versions?

No. Gradle's documentation says catalogs declare requested versions but do not enforce them. Dependency locking records the resolved ones.

Where does Gradle write its lock file?

In gradle.lockfile at the root of each project or subproject directory, and buildscript-gradle.lockfile for the build script classpath.

Turn your gradle.lockfile into a CycloneDX SBOM in seconds. No account, and the file stays in your browser.

Open the free scanner

Sources

This guide summarises Regulation (EU) 2024/2847 for practical use. Tool behaviour was read from each tool's documentation, not tested on your project. It is not legal advice. Related: SBOM for iOS apps · SBOM for Flutter apps · SBOM for React Native apps · Technical documentation.