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
- Automate SBOM Generation: Integrate into your build process
- Include All Dependencies: Don't forget transitive dependencies
- Use Package URLs (purl): Standardize component identification
- Regular Updates: Regenerate SBOMs with each release
- Security Integration: Feed SBOMs into vulnerability scanners
- Version Control: Store SBOMs alongside your source code
- 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.