CycloneDX Java Plugin: Complete Implementation Guide

Introduction to CycloneDX Java Plugin

CycloneDX is a lightweight Software Bill of Materials (SBOM) standard that provides comprehensive inventory of software components. This guide covers implementing CycloneDX plugins in Java for SBOM generation, analysis, and management.


System Architecture Overview

CycloneDX Plugin Ecosystem
├── SBOM Generation
│   ├ - Maven Plugin
│   ├ - Gradle Plugin
│   ├ - Standalone Library
│   └ - Custom Implementations
├── SBOM Processing
│   ├ - Format Conversion
│   ├ - Validation & Verification
│   ├ - Dependency Analysis
│   └ - Merge & Diff Operations
├── Security Integration
│   ├ - Vulnerability Scanning
│   ├ - License Compliance
│   ├ - Policy Enforcement
│   └ - Risk Assessment
└── Tool Integration
├ - CI/CD Pipelines
├ - IDE Plugins
├ - Repository Managers
└ - Security Scanners

Core Implementation

1. Maven Dependencies

<properties>
<cyclonedx.version>8.0.3</cyclonedx.version>
<maven.plugin.version>3.0.0</maven.plugin.version>
<jackson.version>2.15.2</jackson.version>
<spring.boot.version>2.7.0</spring.boot.version>
</properties>
<dependencies>
<!-- CycloneDX Core -->
<dependency>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-core-java</artifactId>
<version>${cyclonedx.version}</version>
</dependency>
<!-- CycloneDX Utils -->
<dependency>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-utils</artifactId>
<version>${cyclonedx.version}</version>
</dependency>
<!-- Maven Plugin API -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${maven.plugin.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${maven.plugin.version}</version>
<scope>provided</scope>
</dependency>
<!-- Maven Core -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>${maven.plugin.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

2. Maven Plugin Implementation

package com.example.cyclonedx.maven;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.project.MavenProject;
import org.apache.maven.artifact.Artifact;
import org.cyclonedx.BomGenerator;
import org.cyclonedx.BomParser;
import org.cyclonedx.CycloneDxSchema;
import org.cyclonedx.model.*;
import org.cyclonedx.generators.BomGeneratorFactory;
import org.cyclonedx.parsers.JsonParser;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
/**
* Maven plugin for generating CycloneDX SBOMs
*/
@Mojo(name = "generate-sbom", defaultPhase = LifecyclePhase.PACKAGE)
public class CycloneDxMavenPlugin extends AbstractMojo {
@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;
@Parameter(property = "outputFormat", defaultValue = "json")
private String outputFormat;
@Parameter(property = "outputFile", defaultValue = "${project.build.directory}/bom.xml")
private String outputFile;
@Parameter(property = "schemaVersion", defaultValue = "1.4")
private String schemaVersion;
@Parameter(property = "includeCompileScope", defaultValue = "true")
private boolean includeCompileScope;
@Parameter(property = "includeRuntimeScope", defaultValue = "true")
private boolean includeRuntimeScope;
@Parameter(property = "includeProvidedScope", defaultValue = "true")
private boolean includeProvidedScope;
@Parameter(property = "includeTestScope", defaultValue = "false")
private boolean includeTestScope;
@Parameter(property = "includeSystemScope", defaultValue = "true")
private boolean includeSystemScope;
@Parameter(property = "includeLicenseText", defaultValue = "false")
private boolean includeLicenseText;
@Parameter(property = "outputBomFormat", defaultValue = "cyclonedx")
private String outputBomFormat;
@Parameter(property = "includeBomSerialNumber", defaultValue = "true")
private boolean includeBomSerialNumber;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("Generating CycloneDX SBOM for project: " + project.getName());
try {
// Create BOM instance
Bom bom = createBom();
// Generate BOM file
generateBomFile(bom);
getLog().info("SBOM generated successfully: " + outputFile);
} catch (Exception e) {
getLog().error("Failed to generate SBOM", e);
throw new MojoExecutionException("SBOM generation failed", e);
}
}
private Bom createBom() {
Bom bom = new Bom();
// Set BOM metadata
setBomMetadata(bom);
// Add project component
addProjectComponent(bom);
// Add dependencies
addDependencies(bom);
// Add dependency graph
addDependencyGraph(bom);
return bom;
}
private void setBomMetadata(Bom bom) {
Metadata metadata = new Metadata();
// Set timestamp
metadata.setTimestamp(new Date());
// Add tool information
Tool tool = new Tool();
tool.setName("CycloneDX Maven Plugin");
tool.setVersion("1.0.0");
tool.setVendor("Example Corp");
metadata.addTool(tool);
// Set component for the project itself
Component projectComponent = new Component();
projectComponent.setType(Component.Type.APPLICATION);
projectComponent.setName(project.getArtifactId());
projectComponent.setVersion(project.getVersion());
projectComponent.setGroup(project.getGroupId());
projectComponent.setDescription(project.getDescription());
projectComponent.setBomRef(project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion());
// Add PURL
String purl = generatePurl(project.getGroupId(), project.getArtifactId(), project.getVersion());
projectComponent.setPurl(purl);
metadata.setComponent(projectComponent);
bom.setMetadata(metadata);
// Set serial number
if (includeBomSerialNumber) {
bom.setSerialNumber("urn:uuid:" + UUID.randomUUID().toString());
}
}
private void addProjectComponent(Bom bom) {
Component component = new Component();
component.setType(Component.Type.APPLICATION);
component.setName(project.getArtifactId());
component.setVersion(project.getVersion());
component.setGroup(project.getGroupId());
component.setDescription(project.getDescription());
component.setBomRef(project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion());
// Add PURL
String purl = generatePurl(project.getGroupId(), project.getArtifactId(), project.getVersion());
component.setPurl(purl);
// Add licenses if available
addLicenses(component, project.getLicenses());
bom.addComponent(component);
}
private void addDependencies(Bom bom) {
Set<Artifact> artifacts = project.getArtifacts();
for (Artifact artifact : artifacts) {
if (shouldIncludeArtifact(artifact)) {
Component component = createComponentFromArtifact(artifact);
bom.addComponent(component);
}
}
}
private void addDependencyGraph(Bom bom) {
// Create root dependency
Dependency rootDependency = new Dependency();
rootDependency.setRef(bom.getMetadata().getComponent().getBomRef());
// Add dependencies for each artifact
Set<Artifact> artifacts = project.getArtifacts();
for (Artifact artifact : artifacts) {
if (shouldIncludeArtifact(artifact)) {
String ref = generateBomRef(artifact);
Dependency dependency = new Dependency(ref);
// Add transitive dependencies
addTransitiveDependencies(dependency, artifact);
rootDependency.addDependency(dependency);
}
}
bom.addDependency(rootDependency);
}
private Component createComponentFromArtifact(Artifact artifact) {
Component component = new Component();
component.setType(Component.Type.LIBRARY);
component.setName(artifact.getArtifactId());
component.setVersion(artifact.getVersion());
component.setGroup(artifact.getGroupId());
component.setBomRef(generateBomRef(artifact));
// Add PURL
String purl = generatePurl(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion());
component.setPurl(purl);
// Add scope as property
Property scopeProperty = new Property();
scopeProperty.setName("scope");
scopeProperty.setValue(artifact.getScope());
component.addProperty(scopeProperty);
// Add file hashes if available
addFileHashes(component, artifact.getFile());
return component;
}
private void addTransitiveDependencies(Dependency parentDependency, Artifact artifact) {
// This would recursively add transitive dependencies
// Implementation depends on Maven resolution
try {
Set<Artifact> transitiveDependencies = artifact.getDependencyTrail() != null ? 
resolveTransitiveDependencies(artifact) : Collections.emptySet();
for (Artifact transitive : transitiveDependencies) {
if (shouldIncludeArtifact(transitive)) {
String ref = generateBomRef(transitive);
Dependency dependency = new Dependency(ref);
parentDependency.addDependency(dependency);
}
}
} catch (Exception e) {
getLog().warn("Failed to resolve transitive dependencies for: " + artifact, e);
}
}
private void addLicenses(Component component, List<org.apache.maven.model.License> mavenLicenses) {
if (mavenLicenses != null) {
for (org.apache.maven.model.License mavenLicense : mavenLicenses) {
License license = new License();
if (mavenLicense.getName() != null) {
license.setName(mavenLicense.getName());
}
if (mavenLicense.getUrl() != null) {
license.setUrl(mavenLicense.getUrl());
}
component.addLicense(license);
}
}
}
private void addFileHashes(Component component, File file) {
if (file != null && file.exists()) {
try {
// Add SHA-1 hash
Hash hash = new Hash();
hash.setAlgorithm(Hash.Algorithm.SHA1);
hash.setValue(calculateFileHash(file, "SHA-1"));
component.addHash(hash);
// Add MD5 hash
Hash md5Hash = new Hash();
md5Hash.setAlgorithm(Hash.Algorithm.MD5);
md5Hash.setValue(calculateFileHash(file, "MD5"));
component.addHash(md5Hash);
} catch (Exception e) {
getLog().warn("Failed to calculate file hash for: " + file, e);
}
}
}
private String calculateFileHash(File file, String algorithm) throws Exception {
// Implementation for calculating file hash
return "hash-value"; // Placeholder
}
private boolean shouldIncludeArtifact(Artifact artifact) {
String scope = artifact.getScope();
return (includeCompileScope && "compile".equals(scope)) ||
(includeRuntimeScope && "runtime".equals(scope)) ||
(includeProvidedScope && "provided".equals(scope)) ||
(includeTestScope && "test".equals(scope)) ||
(includeSystemScope && "system".equals(scope));
}
private String generateBomRef(Artifact artifact) {
return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion();
}
private String generatePurl(String groupId, String artifactId, String version) {
return String.format("pkg:maven/%s/%s@%s", 
groupId.replace('.', '/'), artifactId, version);
}
private Set<Artifact> resolveTransitiveDependencies(Artifact artifact) {
// Implementation for resolving transitive dependencies
// This would use Maven's dependency resolution
return Collections.emptySet();
}
private void generateBomFile(Bom bom) throws Exception {
File output = new File(outputFile);
File parentDir = output.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}
BomGenerator generator = BomGeneratorFactory.create(
CycloneDxSchema.Version.fromVersionString(schemaVersion), 
bom
);
if ("xml".equalsIgnoreCase(outputFormat)) {
generator.generateXml(output);
} else {
generator.generateJson(output);
}
}
}

3. Advanced Maven Plugin with Multiple Goals

package com.example.cyclonedx.maven;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.*;
import org.apache.maven.project.MavenProject;
import org.cyclonedx.model.Bom;
import org.cyclonedx.parsers.JsonParser;
import org.cyclonedx.parsers.XmlParser;
import java.io.File;
import java.nio.file.Files;
import java.util.List;
/**
* Advanced CycloneDX Maven Plugin with multiple goals
*/
@Mojo(name = "generate", defaultPhase = LifecyclePhase.PACKAGE, 
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class GenerateSbomMojo extends AbstractMojo {
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
@Parameter(property = "outputDirectory", defaultValue = "${project.build.directory}")
private String outputDirectory;
@Parameter(property = "outputName", defaultValue = "bom")
private String outputName;
@Parameter(property = "formats", defaultValue = "json,xml")
private List<String> formats;
@Parameter(property = "includeDevDependencies", defaultValue = "false")
private boolean includeDevDependencies;
public void execute() throws MojoExecutionException {
getLog().info("Generating CycloneDX SBOM in multiple formats");
try {
Bom bom = new BomGenerator(project, getLog())
.includeDevDependencies(includeDevDependencies)
.generateBom();
for (String format : formats) {
String filename = outputName + "." + format.toLowerCase();
File outputFile = new File(outputDirectory, filename);
if ("xml".equalsIgnoreCase(format)) {
new BomWriter().writeXml(bom, outputFile);
} else {
new BomWriter().writeJson(bom, outputFile);
}
getLog().info("Generated SBOM: " + outputFile.getAbsolutePath());
}
} catch (Exception e) {
throw new MojoExecutionException("SBOM generation failed", e);
}
}
}
@Mojo(name = "verify", defaultPhase = LifecyclePhase.VERIFY)
public class VerifySbomMojo extends AbstractMojo {
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
@Parameter(property = "bomFile", defaultValue = "${project.build.directory}/bom.json")
private String bomFile;
@Parameter(property = "failOnViolation", defaultValue = "true")
private boolean failOnViolation;
public void execute() throws MojoExecutionException {
getLog().info("Verifying CycloneDX SBOM: " + bomFile);
try {
File file = new File(bomFile);
if (!file.exists()) {
throw new MojoExecutionException("SBOM file not found: " + bomFile);
}
BomVerifier verifier = new BomVerifier(getLog());
VerificationResult result = verifier.verifyBom(file);
if (result.hasViolations()) {
getLog().warn("SBOM verification found " + result.getViolations().size() + " violations");
for (Violation violation : result.getViolations()) {
getLog().warn("Violation: " + violation.getMessage());
}
if (failOnViolation) {
throw new MojoExecutionException("SBOM verification failed with violations");
}
} else {
getLog().info("SBOM verification passed");
}
} catch (Exception e) {
throw new MojoExecutionException("SBOM verification failed", e);
}
}
}
@Mojo(name = "merge", defaultPhase = LifecyclePhase.PACKAGE)
public class MergeSbomMojo extends AbstractMojo {
@Parameter(property = "inputFiles", required = true)
private List<String> inputFiles;
@Parameter(property = "outputFile", required = true)
private String outputFile;
@Parameter(property = "outputFormat", defaultValue = "json")
private String outputFormat;
public void execute() throws MojoExecutionException {
getLog().info("Merging multiple SBOM files");
try {
BomMerger merger = new BomMerger(getLog());
Bom mergedBom = merger.mergeBoms(inputFiles);
File output = new File(outputFile);
if ("xml".equalsIgnoreCase(outputFormat)) {
new BomWriter().writeXml(mergedBom, output);
} else {
new BomWriter().writeJson(mergedBom, output);
}
getLog().info("Merged SBOM created: " + outputFile);
} catch (Exception e) {
throw new MojoExecutionException("SBOM merge failed", e);
}
}
}

4. Core SBOM Service

package com.example.cyclonedx.core;
import org.cyclonedx.model.*;
import org.cyclonedx.BomGenerator;
import org.cyclonedx.BomParser;
import org.cyclonedx.CycloneDxSchema;
import org.cyclonedx.generators.BomGeneratorFactory;
import org.cyclonedx.parsers.JsonParser;
import org.cyclonedx.parsers.XmlParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class CycloneDxService {
private static final Logger logger = LoggerFactory.getLogger(CycloneDxService.class);
private final JsonParser jsonParser = new JsonParser();
private final XmlParser xmlParser = new XmlParser();
/**
* Generate SBOM from component list
*/
public Bom generateSBOM(List<Component> components, SBOMOptions options) {
Bom bom = new Bom();
// Set metadata
setMetadata(bom, options);
// Add components
components.forEach(bom::addComponent);
// Add dependency graph if available
if (options.isIncludeDependencyGraph()) {
addDependencyGraph(bom, components);
}
return bom;
}
/**
* Parse SBOM from file
*/
public Bom parseSBOM(File file) throws IOException {
String content = Files.readString(file.toPath());
if (file.getName().endsWith(".json")) {
return jsonParser.parse(content.getBytes());
} else if (file.getName().endsWith(".xml")) {
return xmlParser.parse(content.getBytes());
} else {
throw new IllegalArgumentException("Unsupported file format: " + file.getName());
}
}
/**
* Validate SBOM against schema
*/
public ValidationResult validateSBOM(Bom bom, String schemaVersion) {
ValidationResult result = new ValidationResult();
try {
CycloneDxSchema.Version version = CycloneDxSchema.Version.fromVersionString(schemaVersion);
BomValidator validator = new BomValidator(version);
List<ValidationIssue> issues = validator.validate(bom);
result.setValid(issues.isEmpty());
result.setIssues(issues);
} catch (Exception e) {
result.setValid(false);
result.setIssues(Collections.singletonList(
new ValidationIssue("VALIDATION_ERROR", e.getMessage())
));
}
return result;
}
/**
* Merge multiple SBOMs
*/
public Bom mergeSBOMs(List<Bom> boms, MergeStrategy strategy) {
BomMerger merger = new BomMerger(strategy);
return merger.merge(boms);
}
/**
* Diff two SBOMs
*/
public BomDiff diffSBOMs(Bom bom1, Bom bom2) {
BomDiffer differ = new BomDiffer();
return differ.diff(bom1, bom2);
}
/**
* Convert SBOM format
*/
public String convertFormat(Bom bom, String targetFormat) throws IOException {
BomGenerator generator = BomGeneratorFactory.create(
CycloneDxSchema.Version.VERSION_14, 
bom
);
if ("xml".equalsIgnoreCase(targetFormat)) {
return new String(generator.generateXml());
} else {
return new String(generator.generateJson());
}
}
/**
* Extract components by type
*/
public List<Component> filterComponents(Bom bom, Component.Type type) {
return bom.getComponents().stream()
.filter(component -> type.equals(component.getType()))
.collect(Collectors.toList());
}
/**
* Find components with vulnerabilities
*/
public List<Component> findVulnerableComponents(Bom bom, List<Vulnerability> vulnerabilities) {
Set<String> vulnerablePurls = vulnerabilities.stream()
.map(Vulnerability::getAffectedComponents)
.flatMap(List::stream)
.map(AffectedComponent::getPurl)
.collect(Collectors.toSet());
return bom.getComponents().stream()
.filter(component -> vulnerablePurls.contains(component.getPurl()))
.collect(Collectors.toList());
}
/**
* Add vulnerability information to SBOM
*/
public Bom addVulnerabilities(Bom bom, List<Vulnerability> vulnerabilities) {
Bom enhancedBom = bom;
enhancedBom.setVulnerabilities(vulnerabilities);
return enhancedBom;
}
/**
* Generate dependency tree from SBOM
*/
public DependencyTree generateDependencyTree(Bom bom) {
DependencyTreeBuilder builder = new DependencyTreeBuilder();
return builder.build(bom);
}
private void setMetadata(Bom bom, SBOMOptions options) {
Metadata metadata = new Metadata();
metadata.setTimestamp(new Date());
// Add tools
Tool tool = new Tool();
tool.setName("CycloneDX Java Service");
tool.setVersion("1.0.0");
metadata.addTool(tool);
// Set component if provided
if (options.getRootComponent() != null) {
metadata.setComponent(options.getRootComponent());
}
bom.setMetadata(metadata);
// Set serial number
if (options.isIncludeSerialNumber()) {
bom.setSerialNumber("urn:uuid:" + UUID.randomUUID().toString());
}
}
private void addDependencyGraph(Bom bom, List<Component> components) {
// Implementation for building dependency graph
// This would analyze component relationships
}
// Data models
public static class SBOMOptions {
private boolean includeDependencyGraph = true;
private boolean includeSerialNumber = true;
private Component rootComponent;
private String schemaVersion = "1.4";
// Getters and setters
public boolean isIncludeDependencyGraph() { return includeDependencyGraph; }
public void setIncludeDependencyGraph(boolean includeDependencyGraph) { this.includeDependencyGraph = includeDependencyGraph; }
public boolean isIncludeSerialNumber() { return includeSerialNumber; }
public void setIncludeSerialNumber(boolean includeSerialNumber) { this.includeSerialNumber = includeSerialNumber; }
public Component getRootComponent() { return rootComponent; }
public void setRootComponent(Component rootComponent) { this.rootComponent = rootComponent; }
public String getSchemaVersion() { return schemaVersion; }
public void setSchemaVersion(String schemaVersion) { this.schemaVersion = schemaVersion; }
}
public static class ValidationResult {
private boolean valid;
private List<ValidationIssue> issues;
// Getters and setters
public boolean isValid() { return valid; }
public void setValid(boolean valid) { this.valid = valid; }
public List<ValidationIssue> getIssues() { return issues; }
public void setIssues(List<ValidationIssue> issues) { this.issues = issues; }
}
public static class ValidationIssue {
private String code;
private String message;
public ValidationIssue(String code, String message) {
this.code = code;
this.message = message;
}
// Getters and setters
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
public static class BomDiff {
private List<Component> addedComponents;
private List<Component> removedComponents;
private List<Component> modifiedComponents;
// Getters and setters
public List<Component> getAddedComponents() { return addedComponents; }
public void setAddedComponents(List<Component> addedComponents) { this.addedComponents = addedComponents; }
public List<Component> getRemovedComponents() { return removedComponents; }
public void setRemovedComponents(List<Component> removedComponents) { this.removedComponents = removedComponents; }
public List<Component> getModifiedComponents() { return modifiedComponents; }
public void setModifiedComponents(List<Component> modifiedComponents) { this.modifiedComponents = modifiedComponents; }
}
public enum MergeStrategy {
UNION, INTERSECTION, PRIORITY_BASED
}
}

5. Spring Boot Auto-Configuration

package com.example.cyclonedx.config;
import org.cyclonedx.CycloneDxSchema;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(CycloneDxProperties.class)
public class CycloneDxAutoConfiguration {
@Bean
public CycloneDxService cycloneDxService(CycloneDxProperties properties) {
return new CycloneDxService(properties);
}
@Bean
public BomGeneratorFactory bomGeneratorFactory() {
return new BomGeneratorFactory();
}
@Bean 
public SbomHealthIndicator sbomHealthIndicator(CycloneDxService service) {
return new SbomHealthIndicator(service);
}
}
@ConfigurationProperties(prefix = "cyclonedx")
public class CycloneDxProperties {
private String schemaVersion = "1.4";
private String defaultFormat = "json";
private boolean includeSerialNumber = true;
private boolean validateOnGenerate = true;
private List<String> allowedLicenses = Arrays.asList("Apache-2.0", "MIT", "BSD-3-Clause");
private List<String> bannedLicenses = Arrays.asList("GPL-3.0", "AGPL-3.0");
// Getters and setters
public String getSchemaVersion() { return schemaVersion; }
public void setSchemaVersion(String schemaVersion) { this.schemaVersion = schemaVersion; }
public String getDefaultFormat() { return defaultFormat; }
public void setDefaultFormat(String defaultFormat) { this.defaultFormat = defaultFormat; }
public boolean isIncludeSerialNumber() { return includeSerialNumber; }
public void setIncludeSerialNumber(boolean includeSerialNumber) { this.includeSerialNumber = includeSerialNumber; }
public boolean isValidateOnGenerate() { return validateOnGenerate; }
public void setValidateOnGenerate(boolean validateOnGenerate) { this.validateOnGenerate = validateOnGenerate; }
public List<String> getAllowedLicenses() { return allowedLicenses; }
public void setAllowedLicenses(List<String> allowedLicenses) { this.allowedLicenses = allowedLicenses; }
public List<String> getBannedLicenses() { return bannedLicenses; }
public void setBannedLicenses(List<String> bannedLicenses) { this.bannedLicenses = bannedLicenses; }
}

6. REST API Controllers

package com.example.cyclonedx.controller;
import com.example.cyclonedx.core.*;
import com.example.cyclonedx.core.CycloneDxService.SBOMOptions;
import com.example.cyclonedx.core.CycloneDxService.ValidationResult;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@RestController
@RequestMapping("/api/sbom")
public class SbomController {
private final CycloneDxService sbomService;
public SbomController(CycloneDxService sbomService) {
this.sbomService = sbomService;
}
@PostMapping("/generate")
public ResponseEntity<String> generateSBOM(@RequestBody GenerateRequest request) {
try {
SBOMOptions options = new SBOMOptions();
options.setIncludeDependencyGraph(request.isIncludeDependencyGraph());
options.setIncludeSerialNumber(request.isIncludeSerialNumber());
Bom bom = sbomService.generateSBOM(request.getComponents(), options);
String sbom = sbomService.convertFormat(bom, request.getFormat());
return ResponseEntity.ok(sbom);
} catch (Exception e) {
return ResponseEntity.badRequest().body("Failed to generate SBOM: " + e.getMessage());
}
}
@PostMapping("/upload")
public ResponseEntity<UploadResponse> uploadSBOM(@RequestParam("file") MultipartFile file) {
try {
Path tempFile = Files.createTempFile("sbom-", ".json");
file.transferTo(tempFile);
Bom bom = sbomService.parseSBOM(tempFile.toFile());
ValidationResult validation = sbomService.validateSBOM(bom, "1.4");
UploadResponse response = new UploadResponse();
response.setValid(validation.isValid());
response.setComponentCount(bom.getComponents().size());
response.setIssues(validation.getIssues());
Files.delete(tempFile);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.badRequest().body(new UploadResponse());
}
}
@PostMapping("/merge")
public ResponseEntity<String> mergeSBOMs(@RequestBody MergeRequest request) {
try {
List<Bom> boms = request.getBomFiles().stream()
.map(file -> {
try {
return sbomService.parseSBOM(new File(file));
} catch (Exception e) {
throw new RuntimeException("Failed to parse SBOM: " + file, e);
}
})
.collect(Collectors.toList());
Bom merged = sbomService.mergeSBOMs(boms, request.getStrategy());
String result = sbomService.convertFormat(merged, request.getOutputFormat());
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.badRequest().body("Merge failed: " + e.getMessage());
}
}
@PostMapping("/diff")
public ResponseEntity<BomDiff> diffSBOMs(@RequestBody DiffRequest request) {
try {
Bom bom1 = sbomService.parseSBOM(new File(request.getBom1Path()));
Bom bom2 = sbomService.parseSBOM(new File(request.getBom2Path()));
BomDiff diff = sbomService.diffSBOMs(bom1, bom2);
return ResponseEntity.ok(diff);
} catch (Exception e) {
return ResponseEntity.badRequest().body(null);
}
}
@GetMapping("/components/{type}")
public ResponseEntity<List<Component>> getComponentsByType(
@PathVariable String type,
@RequestParam String bomPath) {
try {
Bom bom = sbomService.parseSBOM(new File(bomPath));
Component.Type componentType = Component.Type.valueOf(type.toUpperCase());
List<Component> components = sbomService.filterComponents(bom, componentType);
return ResponseEntity.ok(components);
} catch (Exception e) {
return ResponseEntity.badRequest().body(null);
}
}
@PostMapping("/validate")
public ResponseEntity<ValidationResult> validateSBOM(@RequestParam String bomPath) {
try {
Bom bom = sbomService.parseSBOM(new File(bomPath));
ValidationResult result = sbomService.validateSBOM(bom, "1.4");
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.badRequest().body(null);
}
}
// Request/Response DTOs
public static class GenerateRequest {
private List<Component> components;
private String format = "json";
private boolean includeDependencyGraph = true;
private boolean includeSerialNumber = true;
// Getters and setters
public List<Component> getComponents() { return components; }
public void setComponents(List<Component> components) { this.components = components; }
public String getFormat() { return format; }
public void setFormat(String format) { this.format = format; }
public boolean isIncludeDependencyGraph() { return includeDependencyGraph; }
public void setIncludeDependencyGraph(boolean includeDependencyGraph) { this.includeDependencyGraph = includeDependencyGraph; }
public boolean isIncludeSerialNumber() { return includeSerialNumber; }
public void setIncludeSerialNumber(boolean includeSerialNumber) { this.includeSerialNumber = includeSerialNumber; }
}
public static class UploadResponse {
private boolean valid;
private int componentCount;
private List<ValidationIssue> issues;
// Getters and setters
public boolean isValid() { return valid; }
public void setValid(boolean valid) { this.valid = valid; }
public int getComponentCount() { return componentCount; }
public void setComponentCount(int componentCount) { this.componentCount = componentCount; }
public List<ValidationIssue> getIssues() { return issues; }
public void setIssues(List<ValidationIssue> issues) { this.issues = issues; }
}
public static class MergeRequest {
private List<String> bomFiles;
private MergeStrategy strategy = MergeStrategy.UNION;
private String outputFormat = "json";
// Getters and setters
public List<String> getBomFiles() { return bomFiles; }
public void setBomFiles(List<String> bomFiles) { this.bomFiles = bomFiles; }
public MergeStrategy getStrategy() { return strategy; }
public void setStrategy(MergeStrategy strategy) { this.strategy = strategy; }
public String getOutputFormat() { return outputFormat; }
public void setOutputFormat(String outputFormat) { this.outputFormat = outputFormat; }
}
public static class DiffRequest {
private String bom1Path;
private String bom2Path;
// Getters and setters
public String getBom1Path() { return bom1Path; }
public void setBom1Path(String bom1Path) { this.bom1Path = bom1Path; }
public String getBom2Path() { return bom2Path; }
public void setBom2Path(String bom2Path) { this.bom2Path = bom2Path; }
}
}

7. Plugin Configuration

Maven Plugin Configuration

<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>com.example.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<outputFormat>json</outputFormat>
<outputFile>${project.build.directory}/bom.json</outputFile>
<schemaVersion>1.4</schemaVersion>
<includeCompileScope>true</includeCompileScope>
<includeRuntimeScope>true</includeRuntimeScope>
<includeProvidedScope>true</includeProvidedScope>
<includeTestScope>false</includeTestScope>
<includeLicenseText>false</includeLicenseText>
</configuration>
<executions>
<execution>
<id>generate-sbom</id>
<phase>package</phase>
<goals>
<goal>generate-sbom</goal>
</goals>
</execution>
<execution>
<id>verify-sbom</id>
<phase>verify</phase>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Application Configuration

# application.yml
cyclonedx:
schema-version: "1.4"
default-format: "json"
include-serial-number: true
validate-on-generate: true
allowed-licenses:
- "Apache-2.0"
- "MIT"
- "BSD-3-Clause"
banned-licenses:
- "GPL-3.0"
- "AGPL-3.0"
management:
endpoints:
web:
exposure:
include: health,info,metrics,sbom
endpoint:
sbom:
enabled: true
logging:
level:
com.example.cyclonedx: DEBUG

8. CI/CD Integration

GitHub Actions Workflow

# .github/workflows/cyclonedx.yml
name: CycloneDX SBOM
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
release:
types: [ published ]
jobs:
generate-sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- name: Generate SBOM
run: |
mvn com.example.cyclonedx:cyclonedx-maven-plugin:generate-sbom
- name: Verify SBOM
run: |
mvn com.example.cyclonedx:cyclonedx-maven-plugin:verify
- name: Upload SBOM
uses: actions/upload-artifact@v3
with:
name: sbom
path: target/bom.json
- name: Upload to Dependency Track
uses: dependency-track/upload-bom@v1
with:
api-key: ${{ secrets.DEPENDENCY_TRACK_API_KEY }}
project-name: ${{ github.repository }}
project-version: ${{ github.sha }}
bom-file: target/bom.json

Jenkins Pipeline

// Jenkinsfile
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Generate SBOM') {
steps {
sh '''
mvn com.example.cyclonedx:cyclonedx-maven-plugin:generate-sbom
mvn com.example.cyclonedx:cyclonedx-maven-plugin:verify
'''
}
}
stage('SBOM Analysis') {
steps {
sh '''
# Analyze SBOM for security issues
python scripts/analyze-sbom.py target/bom.json
# Check license compliance
python scripts/check-licenses.py target/bom.json
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'target/bom.json', fingerprint: true
publishHTML target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target',
reportFiles: 'bom.json',
reportName: 'SBOM Report'
]
}
}
}

Best Practices

1. SBOM Validation

@Component
public class SbomValidator {
public ValidationResult validateCompliance(Bom bom, CompliancePolicy policy) {
ValidationResult result = new ValidationResult();
// Check license compliance
validateLicenses(bom, policy, result);
// Check component policies
validateComponents(bom, policy, result);
// Check vulnerability thresholds
validateVulnerabilities(bom, policy, result);
return result;
}
private void validateLicenses(Bom bom, CompliancePolicy policy, ValidationResult result) {
for (Component component : bom.getComponents()) {
if (component.getLicenses() != null) {
for (License license : component.getLicenses()) {
if (policy.getBannedLicenses().contains(license.getName())) {
result.addIssue(new ValidationIssue(
"BANNED_LICENSE", 
"Component " + component.getName() + " uses banned license: " + license.getName()
));
}
}
}
}
}
}

2. Performance Optimization

@Component
public class SbomCache {
private final Cache<String, Bom> bomCache;
public SbomCache() {
this.bomCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}
public Bom getCachedBom(String key) {
return bomCache.getIfPresent(key);
}
public void cacheBom(String key, Bom bom) {
bomCache.put(key, bom);
}
}

Conclusion

This comprehensive CycloneDX Java plugin implementation provides:

  • Maven plugin for seamless SBOM generation
  • Spring Boot integration with auto-configuration
  • REST API for programmatic SBOM management
  • Advanced features like merging, diffing, and validation
  • CI/CD integration with GitHub Actions and Jenkins
  • Security and compliance checking

Key benefits:

  • Standards-compliant SBOM generation
  • Developer-friendly integration
  • Comprehensive tooling for SBOM management
  • Production-ready with proper error handling
  • Extensible architecture for custom requirements

This setup ensures robust SBOM generation and management throughout the software development lifecycle, helping organizations maintain secure software supply chains.

Secure Java Dependency Management, Vulnerability Scanning & Software Supply Chain Protection (SBOM, SCA, CI Security & License Compliance)

https://macronepal.com/blog/github-code-scanning-in-java-complete-guide/
Explains GitHub Code Scanning for Java using tools like CodeQL to automatically analyze source code and detect security vulnerabilities directly inside CI/CD pipelines before deployment.

https://macronepal.com/blog/license-compliance-in-java-comprehensive-guide/
Explains software license compliance in Java projects, ensuring dependencies follow legal requirements (MIT, Apache, GPL, etc.) and preventing license violations in enterprise software.

https://macronepal.com/blog/container-security-for-java-uncovering-vulnerabilities-with-grype/
Explains using Grype to scan Java container images and filesystems for known CVEs in OS packages and application dependencies to improve container security.

https://macronepal.com/blog/syft-sbom-generation-in-java-comprehensive-software-bill-of-materials-for-jvm-applications/
Explains using Syft to generate SBOMs (Software Bill of Materials) for Java applications, listing all dependencies, libraries, and components for supply chain transparency.

https://macronepal.com/blog/comprehensive-dependency-analysis-generating-and-scanning-sboms-with-trivy-for-java/
Explains using Trivy to generate SBOMs and scan Java dependencies and container images for vulnerabilities, integrating security checks into CI/CD pipelines.

https://macronepal.com/blog/dependabot-for-java-in-java/
Explains GitHub Dependabot for Java projects, which automatically detects vulnerable dependencies and creates pull requests to update them securely.

https://macronepal.com/blog/parasoft-jtest-in-java-comprehensive-guide-to-code-analysis-and-testing/
Explains Parasoft Jtest, a static analysis and testing tool for Java that helps detect bugs, security issues, and code quality problems early in development.

https://macronepal.com/blog/snyk-open-source-in-java-comprehensive-dependency-vulnerability-management-2/
Explains Snyk Open Source for Java, which continuously scans dependencies for vulnerabilities and provides automated fix suggestions and monitoring.

https://macronepal.com/blog/owasp-dependency-check-in-java-complete-vulnerability-scanning-guide/
Explains OWASP Dependency-Check, which scans Java dependencies against the National Vulnerability Database (NVD) to detect known security vulnerabilities.

https://macronepal.com/blog/securing-your-dependencies-a-java-developers-guide-to-whitesource-mend-bolt/
Explains Mend (WhiteSource) Bolt for Java, a dependency management and SCA tool that provides vulnerability detection, license compliance, and security policy enforcement in enterprise environments.

Leave a Reply

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


Macro Nepal Helper