In a multi projects Gradle build the CycloneDX SBOM is not generated and not added to the boot JAR.

Sample project: https://github.com/fmunch/spring-boot-gradle-cyclonedx (our actual project is a bit more complicated, I tried to keep the bare minimum)

gradle bootJar does not generate a boot JAR containing the SBOM. When looking at the task tree for bootJar the cyclonedxBom task is not present:

> Task :tiTree
:server:bootJar                         (org.springframework.boot.gradle.tasks.bundling.BootJar)
+--- :server:classes                    (org.gradle.api.DefaultTask)
|    +--- :server:compileJava           (org.gradle.api.tasks.compile.JavaCompile)
|    `--- :server:processResources      (org.gradle.language.jvm.tasks.ProcessResources)
+--- :server:compileJava                (org.gradle.api.tasks.compile.JavaCompile)
`--- :server:resolveMainClassName       (org.springframework.boot.gradle.plugin.ResolveMainClassName)
     +--- :server:classes               (org.gradle.api.DefaultTask)
     |    +--- :server:compileJava      (org.gradle.api.tasks.compile.JavaCompile)
     |    `--- :server:processResources (org.gradle.language.jvm.tasks.ProcessResources)
     `--- :server:compileJava           (org.gradle.api.tasks.compile.JavaCompile)


> Task :server:tiTree
:server:bootJar                         (org.springframework.boot.gradle.tasks.bundling.BootJar)
+--- :server:classes                    (org.gradle.api.DefaultTask)
|    +--- :server:compileJava           (org.gradle.api.tasks.compile.JavaCompile)
|    `--- :server:processResources      (org.gradle.language.jvm.tasks.ProcessResources)
+--- :server:compileJava                (org.gradle.api.tasks.compile.JavaCompile)
`--- :server:resolveMainClassName       (org.springframework.boot.gradle.plugin.ResolveMainClassName)
     +--- :server:classes               (org.gradle.api.DefaultTask)
     |    +--- :server:compileJava      (org.gradle.api.tasks.compile.JavaCompile)
     |    `--- :server:processResources (org.gradle.language.jvm.tasks.ProcessResources)
     `--- :server:compileJava           (org.gradle.api.tasks.compile.JavaCompile)

When debugging org.springframework.boot.gradle.plugin.SpringBootPlugin it seems that CycloneDxPlugin is not in the classpath, preventing CycloneDxPluginActionfrom configuring CycloneDxTask.

Even if we add the following, cyclonedxBom appears in the task tree and generates server/build/reports/bom.* but CycloneDxPlugin is still missing from the classpath and the SBOM is missing in the JAR and manifest.

tasks.named('bootJar') {
    dependsOn cyclonedxBom
}

Comment From: wilkinsona

Thanks for the minimal reproducer.

The problem's due to the structure of your project and the effect that has on the class loaders that Gradle creates. With Spring Boot's plugin being a dependency of buildSrc and the CycloneDX plugin being a plugin dependency of a project, they're loaded by different class loaders. This means that the Spring Boot plugin cannot load the CycloneDX plugin's classes which prevents it from being able to react to the plugin being applied. Unfortunately, there's nothing that Spring Boot can do about this as it's Gradle that controls the class loaders and their hierarchy.

You can fix the problem by adding a dependency on the CycloneDX plugin to buildSrc. The plugin still has to be applied in the server project but you can remove the version and Gradle will automatically use the version from buildSrc:

diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 652faac..d5fd2b8 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -5,6 +5,7 @@ plugins {

 repositories {
     mavenCentral()
+    gradlePluginPortal()
 }

 dependencies {
@@ -14,4 +15,5 @@ dependencies {

     implementation 'org.springframework.boot:spring-boot-gradle-plugin:3.3.4'
     implementation 'io.spring.gradle:dependency-management-plugin:1.1.6'
+    implementation 'org.cyclonedx:cyclonedx-gradle-plugin:1.10.0'
 }
diff --git a/server/build.gradle b/server/build.gradle
index 3990375..486d825 100644
--- a/server/build.gradle
+++ b/server/build.gradle
@@ -1,6 +1,6 @@
 plugins {
     id 'custom.spring'
-    id 'org.cyclonedx.bom' version '1.10.0'
+    id 'org.cyclonedx.bom'
 }

With these changes in place, the cyclonedxBom task will be run when executing bootJar:

$ ./gradlew bootJar --console=plain 
> Task :buildSrc:extractPluginRequests UP-TO-DATE
> Task :buildSrc:generatePluginAdapters UP-TO-DATE
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:compileGroovyPlugins UP-TO-DATE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :server:compileJava
> Task :server:cyclonedxBom
> Task :server:processResources
> Task :server:classes
> Task :server:resolveMainClassName
> Task :server:bootJar

BUILD SUCCESSFUL in 2s
12 actionable tasks: 5 executed, 7 up-to-date

And the bom's included in the jar:

$ unzip -l server/build/libs/server-0.0.1-SNAPSHOT.jar | grep cdx 
    61768  10-11-2024 14:53   META-INF/sbom/application.cdx.json

Comment From: fmunch

Thank you for taking the time to write a detailed answer. Adding the dependency to buildSrc fixed both issues.