> Learn how to generate Software Bill of Materials for Scala projects. Complete guide with build.sbt examples, cross-compilation, and dependency management.

- **Updated:** 2026-08-31

## Source vs Build SBOMs

Scala's primary build tool, sbt (Scala Build Tool), manages dependencies through `build.sbt`. Unlike some other ecosystems, sbt doesn't use a traditional lockfile by default, which creates challenges for SBOM generation.

- **Source SBOMs** are generated from `build.sbt` and resolved dependencies
- **Build SBOMs** analyze compiled artifacts in the `target/` directory

For accurate SBOMs, you need to resolve dependencies first using sbt plugins.

## Dependency Declaration

### build.sbt

Dependencies are declared in `build.sbt`:

```scala
name := "my-app"
version := "1.0.0"
scalaVersion := "3.3.1"

libraryDependencies ++= Seq(
  "org.typelevel" %% "cats-core" % "2.10.0",
  "io.circe" %% "circe-core" % "0.14.6",
  "io.circe" %% "circe-generic" % "0.14.6",
  "io.circe" %% "circe-parser" % "0.14.6",
  "org.http4s" %% "http4s-ember-server" % "0.23.24",
  "org.http4s" %% "http4s-circe" % "0.23.24",
  "org.http4s" %% "http4s-dsl" % "0.23.24",

  // Test dependencies
  "org.scalatest" %% "scalatest" % "3.2.17" % Test,
  "org.scalamock" %% "scalamock" % "5.2.0" % Test
)
```

### Understanding %% vs %

```scala
// %% appends Scala version (cross-compiled)
"org.typelevel" %% "cats-core" % "2.10.0"
// Resolves to: cats-core_3 or cats-core_2.13

// % uses artifact as-is (Java libraries)
"org.postgresql" % "postgresql" % "42.7.0"
```

## Cross-Compilation

Scala libraries are often published for multiple Scala versions:

```scala
// project/build.properties
sbt.version=1.9.8

// build.sbt
crossScalaVersions := Seq("2.13.12", "3.3.1")
scalaVersion := "3.3.1"
```

This means dependencies resolve to different artifacts based on Scala version:

- `cats-core_2.13` for Scala 2.13
- `cats-core_3` for Scala 3

Your SBOM should reflect the specific Scala version used for your build.

## sbt-dependency-graph Plugin

For detailed dependency analysis, use the sbt-dependency-graph plugin:

```scala
// project/plugins.sbt
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
```

Generate dependency information:

```bash
# ASCII tree
sbt dependencyTree

# Export to file
sbt "dependencyList" > dependencies.txt

# Generate DOT graph
sbt dependencyDot
```

## sbt-dependency-lock Plugin

For lockfile-based reproducibility:

```scala
// project/plugins.sbt
addSbtPlugin("io.github.davidgregory084" % "sbt-dependency-lock" % "1.1.0")
```

```bash
# Generate lockfile
sbt dependencyLockWrite

# Verify against lockfile
sbt dependencyLockCheck
```

This creates `build.sbt.lock`:

```
# This file was generated by sbt-dependency-lock
org.scala-lang:scala-library:2.13.12
org.typelevel:cats-core_2.13:2.10.0
org.typelevel:cats-kernel_2.13:2.10.0
```

## Generating an SBOM

SBOM generation is the first step in the [SBOM lifecycle](https://sbomify.com/features/generate-collaborate-analyze/). After generation, you typically need to enrich your SBOM with package metadata and augment it with your organization's details.

### Using sbomify GitHub Action (Recommended)

The [sbomify action](https://sbomify.com/sbomify-action/) is a swiss army knife for SBOMs that automatically selects the best generation tool for your ecosystem, enriches the output with package metadata, and optionally augments it with your business information – all in one step. It runs on [any CI platform](https://sbomify.com/sbomify-action/runtimes/), not just GitHub, and the source is [on GitHub](https://github.com/sbomify/sbomify-action/).

For Scala, sbomify uses **cyclonedx-sbt** under the hood.

**Standalone (no account needed):**

```yaml
- uses: sbomify/sbomify-action@master
  env:
    LOCK_FILE: build.sbt
    OUTPUT_FILE: sbom.cdx.json
    COMPONENT_NAME: my-scala-app
    COMPONENT_VERSION: ${{ github.ref_name }}
    ENRICH: true
    UPLOAD: false
```

Using `github.ref_name` automatically captures your git tag (e.g., `v1.2.3`) as the SBOM version. For rolling releases without tags, use `github.sha` instead. See our [SBOM versioning guide](https://sbomify.com/guides/how-to-version-sboms/) for best practices.

**With sbomify platform (adds augmentation and upload):**

```yaml
- uses: sbomify/sbomify-action@master
  env:
    TOKEN: ${{ secrets.SBOMIFY_TOKEN }}
    COMPONENT_ID: my-component-id
    LOCK_FILE: build.sbt
    OUTPUT_FILE: sbom.cdx.json
    AUGMENT: true
    ENRICH: true
```

### Alternative Tools

If you prefer to run SBOM generation tools manually:

**cdxgen (recommended for manual use):**

```bash
npm install -g @cyclonedx/cdxgen
cdxgen -t sbt -o sbom.cdx.json
```

**sbt-sbom plugin:**

```scala
// project/plugins.sbt
addSbtPlugin("com.github.sbt" % "sbt-sbom" % "0.4.0")
```

```bash
sbt sbom
```

When using these tools directly, you'll need to handle enrichment and augmentation separately.

### GitLab CI

```yaml
generate-sbom:
  image: ghcr.io/sbomify/sbomify-action
  variables:
    LOCK_FILE: build.sbt
    OUTPUT_FILE: sbom.cdx.json
    UPLOAD: "false"
    ENRICH: "true"
  script:
    - sbomify-action
  artifacts:
    paths:
      - sbom.cdx.json
```

## Multi-Module Projects

Scala projects often have multiple modules:

```scala
// build.sbt
lazy val root = (project in file("."))
  .aggregate(core, api, web)

lazy val core = (project in file("core"))
  .settings(
    libraryDependencies ++= Seq(
      "org.typelevel" %% "cats-core" % "2.10.0"
    )
  )

lazy val api = (project in file("api"))
  .dependsOn(core)
  .settings(
    libraryDependencies ++= Seq(
      "io.circe" %% "circe-core" % "0.14.6"
    )
  )

lazy val web = (project in file("web"))
  .dependsOn(api)
  .settings(
    libraryDependencies ++= Seq(
      "org.http4s" %% "http4s-ember-server" % "0.23.24"
    )
  )
```

Generate SBOMs per module or aggregate:

```bash
# All modules
sbt dependencyTree

# Specific module
sbt "project api" dependencyTree
```

## Handling Special Cases

### Resolvers and Private Repositories

```scala
resolvers ++= Seq(
  "Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/",
  "Private Repo" at "https://repo.example.com/maven2"
)

credentials += Credentials(Path.userHome / ".sbt" / ".credentials")
```

### Exclusions and Overrides

```scala
libraryDependencies ++= Seq(
  "org.example" %% "library" % "1.0.0" exclude("commons-logging", "commons-logging")
)

// Force specific version
dependencyOverrides += "com.google.guava" % "guava" % "32.1.3-jre"
```

### Provided Dependencies

```scala
libraryDependencies ++= Seq(
  "javax.servlet" % "javax.servlet-api" % "4.0.1" % Provided
)
```

Provided dependencies should be documented but noted as provided by the runtime environment.

### Compile-time Only

```scala
libraryDependencies ++= Seq(
  "org.wartremover" %% "wartremover" % "3.1.6" % "compile->compile"
)
```

## Best Practices

1. **Use dependency locking** - Install sbt-dependency-lock for reproducibility
2. **Pin Scala version** - Be explicit about scalaVersion
3. **Document cross-compilation** - Note which Scala version your SBOM represents
4. **Resolve before generating** - Run `sbt update` before SBOM generation
5. **Separate test dependencies** - Use `% Test` scope appropriately
6. **Check for updates** - Use `sbt dependencyUpdates` with sbt-updates plugin

## Security Scanning

Use sbt-dependency-check for vulnerability scanning:

```scala
// project/plugins.sbt
addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "5.1.0")
```

```bash
# Run vulnerability check
sbt dependencyCheck

# Generate report
sbt dependencyCheckReport
```

Combine with SBOM generation:

```yaml
- name: Security check
  run: sbt dependencyCheck

- name: Generate SBOM
  uses: sbomify/sbomify-action@master
  env:
    LOCK_FILE: 'build.sbt'
    OUTPUT_FILE: 'sbom.cdx.json'
```

## Mill Build Tool

For projects using Mill instead of sbt:

```scala
// build.sc
import mill._
import mill.scalalib._

object myapp extends ScalaModule {
  def scalaVersion = "3.3.1"
  def ivyDeps = Agg(
    ivy"org.typelevel::cats-core:2.10.0",
    ivy"io.circe::circe-core:0.14.6"
  )
}
```

Mill has different tooling but similar SBOM approaches:

```bash
mill show myapp.ivyDepsTree
```

## Further Resources

For more SBOM tools and resources, see our [SBOM Resources](https://sbomify.com/resources/) page, which includes general SBOM utilities for generation, distribution, and analysis.

---

From sbomify: https://sbomify.com/guides/scala/
