Source vs Build SBOMs
Java has a mature dependency management ecosystem with Maven and Gradle as the primary build tools. Understanding the difference between source and build SBOMs is particularly important in Java due to:
- Transitive dependency resolution: Java’s dependency managers resolve complex transitive dependency trees
- Scope differences: Dependencies can be compile-time, runtime, test, or provided
- Multi-module projects: Enterprise Java applications often consist of many modules
For source SBOMs, you’re generating from pom.xml, build.gradle, or gradle.lockfile. For build SBOMs, you’re analyzing the actual JARs in your classpath or the final packaged artifact.
Lockfile Deep Dive
Maven (pom.xml)
Maven’s pom.xml is both a manifest and a build file. Dependencies are declared with optional version ranges:
<project>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.0</version>
</dependency>
</dependencies>
</project>
Important considerations:
- Maven doesn’t have a traditional lockfile - versions in
pom.xmlcan be ranges or managed by parent POMs - Use the
maven-enforcer-pluginwith<dependencyConvergence>to ensure consistent versions - Consider using a BOM (Bill of Materials) POM for version management
To get the effective dependencies with resolved versions:
mvn dependency:tree
mvn dependency:list
Gradle (build.gradle / build.gradle.kts)
Gradle supports both Groovy (build.gradle) and Kotlin (build.gradle.kts) DSLs:
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
// build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web:3.2.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
Gradle Lockfile (gradle.lockfile)
For reproducible builds, Gradle supports dependency locking:
# Enable dependency locking
gradle dependencies --write-locks
This creates a gradle.lockfile:
# This is a Gradle generated file for dependency locking.
com.fasterxml.jackson.core:jackson-annotations:2.16.0=compileClasspath,runtimeClasspath
com.fasterxml.jackson.core:jackson-core:2.16.0=compileClasspath,runtimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.16.0=compileClasspath,runtimeClasspath
org.springframework.boot:spring-boot-starter-web:3.2.0=compileClasspath,runtimeClasspath
To enable locking in your build.gradle:
dependencyLocking {
lockAllConfigurations()
}
Transitive Dependency Resolution
Both Maven and Gradle resolve transitive dependencies, but with different strategies:
| Aspect | Maven | Gradle |
|---|---|---|
| Conflict resolution | Nearest definition wins | Newest version wins (default) |
| Dependency scope | compile, provided, runtime, test | implementation, api, compileOnly, runtimeOnly, testImplementation |
| BOM support | <dependencyManagement> | platform() |
Understanding these differences is crucial because your SBOM should accurately reflect what ends up in your final artifact.
Multi-Module Projects
Enterprise Java applications often use multi-module structures:
my-app/
├── pom.xml (parent)
├── core/
│ └── pom.xml
├── api/
│ └── pom.xml
└── web/
└── pom.xml
For SBOM generation, you have two approaches:
- Per-module SBOMs: Generate an SBOM for each module
- Aggregate SBOM: Generate a single SBOM for the entire project
Generating an SBOM
SBOM generation is the first step in the SBOM lifecycle. 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 GitHub 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.
For Java, sbomify uses cdxgen under the hood as it has the best support for Maven and Gradle projects.
Standalone (no account needed):
- uses: sbomify/github-action@master
env:
LOCK_FILE: pom.xml
OUTPUT_FILE: sbom.cdx.json
COMPONENT_NAME: my-java-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 for best practices.
With sbomify platform (adds augmentation and upload):
- uses: sbomify/github-action@master
env:
TOKEN: ${{ secrets.SBOMIFY_TOKEN }}
COMPONENT_ID: my-component-id
LOCK_FILE: pom.xml
OUTPUT_FILE: sbom.cdx.json
AUGMENT: true
ENRICH: true
The action supports: pom.xml, build.gradle, build.gradle.kts, and gradle.lockfile.
Alternative Tools
If you prefer to run SBOM generation tools manually:
cdxgen (recommended for manual use):
npm install -g @cyclonedx/cdxgen
cdxgen -t maven -o sbom.cdx.json
CycloneDX Maven Plugin:
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>2.9.1</version>
</plugin>
mvn cyclonedx:makeAggregateBom
CycloneDX Gradle Plugin:
plugins {
id 'org.cyclonedx.bom' version '3.1.0'
}
gradle cyclonedxBom
Trivy:
trivy fs --format cyclonedx --output sbom.cdx.json .
When using these tools directly, you’ll need to handle enrichment and augmentation separately.
GitLab CI
generate-sbom:
image: sbomifyhub/sbomify-action
variables:
LOCK_FILE: pom.xml
OUTPUT_FILE: sbom.cdx.json
UPLOAD: "false"
ENRICH: "true"
script:
- /sbomify.sh
artifacts:
paths:
- sbom.cdx.json
Best Practices
- Use dependency locking - Enable Gradle lockfiles for reproducible builds
- Pin versions explicitly - Avoid version ranges in production dependencies
- Use BOMs for version management - Spring Boot BOM, Jackson BOM, etc.
- Separate compile and runtime scopes - Be clear about what goes into your final artifact
- Exclude test dependencies - Unless required for compliance
- Consider shaded/uber JARs - These need special handling for accurate SBOMs
Handling Shaded/Uber JARs
If you use the Maven Shade Plugin or Gradle Shadow Plugin to create uber JARs, the dependencies are bundled inside. For accurate SBOMs:
- Generate the SBOM before shading
- Use tools that can analyze the shaded JAR contents
- Consider using cdxgen’s
--deepflag:
cdxgen -t java --deep -o sbom.cdx.json
Further Resources
For more SBOM tools and resources, see our SBOM Resources page, which includes additional Java-specific tools like CycloneDX Java and SPDX Java libraries.