Software Bill of Materials: Mastering SPDX for Java Application Security

In today's complex software supply chain, knowing exactly what's in your application is no longer just good practice—it's a critical security requirement. The Software Package Data Exchange (SPDX) standard provides a universal framework for communicating software component information, and for Java developers managing dependencies from Maven Central, it's becoming essential. This article explores the SPDX standard and how it helps Java developers create comprehensive Software Bills of Materials (SBOMs).


The Software Supply Chain Challenge

Modern Java applications typically depend on dozens or even hundreds of third-party components:

<!-- A typical Spring Boot application has 50+ transitive dependencies -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

The Problems:

  • Security Vulnerabilities: New vulnerabilities are discovered daily in open-source libraries
  • License Compliance: Ensuring all dependencies comply with organizational policies
  • Dependency Confusion: Knowing which versions are actually deployed
  • Audit Requirements: Regulatory demands for software transparency

What is SPDX?

SPDX (Software Package Data Exchange) is an open standard format for communicating software bill of materials information. It's maintained by the Linux Foundation and has become the de facto standard for SBOMs, recognized by organizations like NTIA, CISA, and the US government.

Key SPDX Concepts:

  • Standardized Format: Machine-readable format (JSON, YAML, RDF, Tag:Value)
  • Comprehensive Metadata: Covers components, licenses, security, and relationships
  • Tooling Ecosystem: Wide support across vulnerability scanners and compliance tools
  • Industry Adoption: Used by major companies and government agencies

SPDX Document Structure

An SPDX document contains several key sections:

SPDX Document
├── Creation Info (who, when, how)
├── Package Information (components)
├── File Information (optional)
├── Snippet Information (optional)
├── License Information
├── Relationship Information
└── Annotations (comments)

SPDX in Action: Java Examples

1. Basic SPDX Document Structure (JSON)

{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "Example-Java-App-1.0.0",
"documentNamespace": "https://example.com/spdx/Example-Java-App-1.0.0",
"creationInfo": {
"created": "2024-01-15T10:00:00Z",
"creators": [
"Tool: org.cyclonedx-maven-plugin-2.7.0",
"Organization: Example Corp"
]
},
"packages": [
{
"name": "Example Java Application",
"SPDXID": "SPDXRef-Package-Example-Java-App-1.0.0",
"versionInfo": "1.0.0",
"packageFileName": "example-app-1.0.0.jar",
"supplier": "Organization: Example Corp",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"licenseConcluded": "Apache-2.0",
"licenseDeclared": "Apache-2.0",
"copyrightText": "NOASSERTION"
}
]
}

2. Representing Java Dependencies in SPDX

{
"packages": [
{
"name": "spring-boot-starter-web",
"SPDXID": "SPDXRef-Package-spring-boot-starter-web-2.7.0",
"versionInfo": "2.7.0",
"supplier": "Organization: VMware",
"downloadLocation": "https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-web/2.7.0/",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/org.springframework.boot/[email protected]"
}
],
"licenseConcluded": "Apache-2.0",
"licenseDeclared": "Apache-2.0"
},
{
"name": "h2",
"SPDXID": "SPDXRef-Package-h2-2.1.214",
"versionInfo": "2.1.214",
"supplier": "Organization: H2 Database",
"downloadLocation": "https://repo1.maven.org/maven2/com/h2database/h2/2.1.214/",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/com.h2database/[email protected]"
}
],
"licenseConcluded": "MPL-2.0",
"licenseDeclared": "MPL-2.0"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-Example-Java-App-1.0.0",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Package-spring-boot-starter-web-2.7.0"
},
{
"spdxElementId": "SPDXRef-Package-Example-Java-App-1.0.0", 
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Package-h2-2.1.214"
}
]
}

Generating SPDX SBOMs for Java Projects

1. Using CycloneDX Maven Plugin

<!-- Add to your pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>2.7.9</version>
<configuration>
<projectType>library</projectType>
<schemaVersion>1.4</schemaVersion>
<includeBomSerialNumber>true</includeBomSerialNumber>
<includeCompileScope>true</includeCompileScope>
<includeProvidedScope>true</includeProvidedScope>
<includeRuntimeScope>true</includeRuntimeScope>
<includeTestScope>false</includeTestScope>
<includeSystemScope>true</includeSystemScope>
<outputFormat>json</outputFormat>
<outputName>sbom</outputName>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>makeAggregateBom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Generate the SBOM:

mvn clean package
# Generates target/sbom.json in SPDX format

2. Using Gradle CycloneDX Plugin

// build.gradle.kts
plugins {
id("org.cyclonedx.bom") version "1.7.4"
}
cyclonedxBom {
projectType.set("library")
schemaVersion.set("1.4")
outputFormat.set("json")
includeBomSerialNumber.set(true)
includeLicenseText.set(false)
outputName.set("sbom")
}

Generate the SBOM:

./gradlew cyclonedxBom
# Generates build/reports/bom.json in SPDX format

Programmatic SPDX Generation in Java

For custom SBOM generation, you can use SPDX Java libraries:

Maven Dependency:

<dependency>
<groupId>org.spdx</groupId>
<artifactId>spdx-java-library</artifactId>
<version>1.1.6</version>
</dependency>

Java Code Example:

import org.spdx.library.InvalidSPDXAnalysisException;
import org.spdx.library.ModelCopyManager;
import org.spdx.library.Version;
import org.spdx.library.model.SpdxDocument;
import org.spdx.library.model.SpdxPackage;
import org.spdx.storage.lifecycle.LifecyclePolicy;
import org.spdx.storage.simple.InMemSpdxStore;
import org.spdx.jackson.DefaultModelStore;
import java.util.Arrays;
import java.util.Date;
public class SpdxBomGenerator {
public SpdxDocument createSpdxDocument() throws InvalidSPDXAnalysisException {
// Initialize model store
DefaultModelStore modelStore = new DefaultModelStore();
ModelCopyManager copyManager = new ModelCopyManager();
LifecyclePolicy lifecyclePolicy = new LifecyclePolicy();
// Create SPDX document
SpdxDocument document = modelStore.createSpdxDocument(
"SPDXRef-DOCUMENT", 
"Example Java App SBOM", 
"https://example.com/spdx/Example-Java-App-1.0.0"
);
// Set creation info
document.getCreationInfo()
.setCreated(new Date())
.setCreators(Arrays.asList("Tool: Custom-SBOM-Generator-1.0", "Organization: Example Corp"))
.setDataLicense("CC0-1.0");
return document;
}
public SpdxPackage createPackage(SpdxDocument document, 
String name, String version, 
String license, String purl) throws InvalidSPDXAnalysisException {
String spdxId = "SPDXRef-Package-" + name.replaceAll("[^a-zA-Z0-9]", "-") + "-" + version;
SpdxPackage pkg = document.createPackage(
spdxId, 
name, 
license, 
"NOASSERTION", 
document.getCreationInfo().getCreated()
);
pkg.setVersion(version);
pkg.setDownloadLocation("NOASSERTION");
// Add Package URL (purl) external reference
pkg.getExternalRefs().add(
document.createExternalRef(
"PACKAGE-MANAGER", 
"purl", 
purl, 
null
)
);
return pkg;
}
public static void main(String[] args) {
try {
SpdxBomGenerator generator = new SpdxBomGenerator();
SpdxDocument document = generator.createSpdxDocument();
// Add Spring Boot dependency
SpdxPackage springBoot = generator.createPackage(
document,
"spring-boot-starter-web",
"2.7.0",
"Apache-2.0",
"pkg:maven/org.springframework.boot/[email protected]"
);
// Add H2 database dependency  
SpdxPackage h2 = generator.createPackage(
document,
"h2",
"2.1.214", 
"MPL-2.0",
"pkg:maven/com.h2database/[email protected]"
);
System.out.println("SPDX Document created with " + 
document.getDocumentDescribes().size() + " packages");
} catch (InvalidSPDXAnalysisException e) {
e.printStackTrace();
}
}
}

Integrating SPDX with CI/CD Pipelines

GitHub Actions Example:

name: Generate SPDX SBOM
on:
push:
branches: [ main ]
release:
types: [ published ]
jobs:
generate-sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Generate SPDX SBOM with Maven
run: mvn org.cyclonedx:cyclonedx-maven-plugin:2.7.9:makeAggregateBom
- name: Upload SPDX SBOM
uses: actions/upload-artifact@v3
with:
name: spdx-sbom
path: target/sbom.json
retention-days: 30
- name: Security Scan with SPDX
uses: advanced-security/spdx-scan-action@v1
with:
sbom-file: target/sbom.json

Validating SPDX Documents

Using SPDX Java Tools for Validation:

import org.spdx.tools.SpdxToolsHelper;
public class SpdxValidator {
public boolean validateSpdxJson(String filePath) {
try {
SpdxToolsHelper.serializeFileToTagValue(filePath, "temp.spdx");
return true;
} catch (Exception e) {
System.err.println("SPDX validation failed: " + e.getMessage());
return false;
}
}
public static void main(String[] args) {
SpdxValidator validator = new SpdxValidator();
boolean isValid = validator.validateSpdxJson("sbom.json");
System.out.println("SPDX document is valid: " + isValid);
}
}

Best Practices for SPDX in Java Projects

  1. Automate SBOM Generation: Integrate into your build process
  2. Include All Dependencies: Don't forget transitive dependencies
  3. Use Package URLs (purl): Standardize component identification
  4. Regular Updates: Regenerate SBOMs with each release
  5. Security Integration: Feed SBOMs into vulnerability scanners
  6. Version Control: Store SBOMs alongside your source code
  7. Signature Verification: Sign your SBOMs for authenticity

Benefits for Java Development Teams

  • Faster Vulnerability Response: Quickly identify affected components
  • License Compliance: Avoid legal issues with incompatible licenses
  • Supply Chain Security: Meet regulatory requirements (EO 14028, etc.)
  • Audit Readiness: Maintain accurate software inventories
  • Dependency Management: Understand and optimize your dependency tree

Conclusion

The SPDX standard provides Java developers with a powerful framework for managing software supply chain security. By adopting SPDX SBOMs:

  • Security Teams can quickly identify vulnerable components
  • Legal Teams can ensure license compliance
  • Development Teams can understand their dependency graph
  • Operations Teams can maintain accurate deployment records

With excellent tooling support through plugins for Maven, Gradle, and dedicated Java libraries, integrating SPDX into your Java development workflow has never been easier. As software supply chain security becomes increasingly critical, SPDX SBOMs transition from being a best practice to an essential component of modern Java application development.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper