The CycloneDX plugin integration changed behavior between spring boot 3.5.3 and 3.5.4, where the number of direct dependencies increased.
Reproduce
Spring initializer, new project (only 3.5.4 available), select some dependency like Spring WEB.
Add the cyclonedx gradle plugin
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.4'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.cyclonedx.bom' version '2.3.1' // added
}
duplicate the project directory to and change from 3.5.4 to 3.5.3 in the new one
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.3' // downgraded
id 'io.spring.dependency-management' version '1.1.7'
id 'org.cyclonedx.bom' version '2.3.1'
}
Now run the cyclonedxBom
gradle task in both.
Diffing the two build/reports/application.cdx.json
shows many differences (naturally since a lot of boms were bumped). However, the root pkg:maven/com.example/demo@0.0.1-SNAPSHOT?project_path=%3A
in the dependencies section declares very different sets of direct dependencies
The regression / change
v3.5.3:
{
...
"dependencies": [
...
{
"ref" : "pkg:maven/com.example/demo@0.0.1-SNAPSHOT?project_path=%3A",
"dependsOn" : [
"pkg:maven/org.springframework.boot/spring-boot-starter-web@3.5.3?type=jar",
"pkg:maven/org.springframework.boot/spring-boot-starter-test@3.5.3?type=jar",
"pkg:maven/org.junit.platform/junit-platform-launcher@1.12.2?type=jar"
]
},
...
],
...
}
v3.5.4:
{
...
"dependencies": [
...
{
"ref" : "pkg:maven/com.example/demo@0.0.1-SNAPSHOT?project_path=%3A",
"dependsOn" : [
"pkg:maven/org.springframework.boot/spring-boot-starter-tomcat@3.5.4?type=jar",
"pkg:maven/org.springframework/spring-jcl@6.2.9?type=jar",
"pkg:maven/ch.qos.logback/logback-core@1.5.18?type=jar",
"pkg:maven/org.yaml/snakeyaml@2.4?type=jar",
"pkg:maven/io.micrometer/micrometer-observation@1.15.2?type=jar",
"pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.19.2?type=jar",
"pkg:maven/org.springframework/spring-beans@6.2.9?type=jar",
"pkg:maven/org.slf4j/jul-to-slf4j@2.0.17?type=jar",
"pkg:maven/org.slf4j/slf4j-api@2.0.17?type=jar",
"pkg:maven/org.springframework.boot/spring-boot-starter-web@3.5.4?type=jar",
"pkg:maven/org.springframework.boot/spring-boot-starter-test@3.5.4?type=jar",
"pkg:maven/org.springframework.boot/spring-boot-starter-json@3.5.4?type=jar",
"pkg:maven/jakarta.annotation/jakarta.annotation-api@2.1.1?type=jar",
"pkg:maven/org.springframework.boot/spring-boot@3.5.4?type=jar",
"pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jsr310@2.19.2?type=jar",
"pkg:maven/org.apache.tomcat.embed/tomcat-embed-el@10.1.43?type=jar",
"pkg:maven/org.springframework.boot/spring-boot-starter@3.5.4?type=jar",
"pkg:maven/org.apache.tomcat.embed/tomcat-embed-websocket@10.1.43?type=jar",
"pkg:maven/com.fasterxml.jackson.module/jackson-module-parameter-names@2.19.2?type=jar",
"pkg:maven/com.fasterxml.jackson.datatype/jackson-datatype-jdk8@2.19.2?type=jar",
"pkg:maven/org.junit.platform/junit-platform-launcher@1.12.2?type=jar",
"pkg:maven/io.micrometer/micrometer-commons@1.15.2?type=jar",
"pkg:maven/org.springframework.boot/spring-boot-starter-logging@3.5.4?type=jar",
"pkg:maven/ch.qos.logback/logback-classic@1.5.18?type=jar",
"pkg:maven/org.springframework/spring-context@6.2.9?type=jar",
"pkg:maven/org.apache.logging.log4j/log4j-to-slf4j@2.24.3?type=jar",
"pkg:maven/org.apache.logging.log4j/log4j-api@2.24.3?type=jar",
"pkg:maven/org.springframework.boot/spring-boot-autoconfigure@3.5.4?type=jar",
"pkg:maven/org.springframework/spring-aop@6.2.9?type=jar",
"pkg:maven/com.fasterxml.jackson.core/jackson-annotations@2.19.2?type=jar",
"pkg:maven/org.springframework/spring-expression@6.2.9?type=jar",
"pkg:maven/org.apache.tomcat.embed/tomcat-embed-core@10.1.43?type=jar",
"pkg:maven/org.springframework/spring-core@6.2.9?type=jar",
"pkg:maven/org.springframework/spring-web@6.2.9?type=jar",
"pkg:maven/com.fasterxml.jackson.core/jackson-core@2.19.2?type=jar",
"pkg:maven/org.springframework/spring-webmvc@6.2.9?type=jar"
]
},
...
],
...
}
Both project directories just have this in their dependecies block:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
This new behavior makes it more difficult to determine what is an explicit direct dependency and what the spring boot plugin appears to inject as additional direct dependencies.
The behavior is the same in Snapshots: - 3.5.5-20250820.075853-49 - 4.0.0-20250820.081557-416
To run snapshots
Also change settings.gradle and build.gradle to include spring snapshots:# settings.gradle
pluginManagement {
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/snapshot' }
}
}
rootProject.name = 'demo'
# build.gradle
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/snapshot' }
}
Expected
No behavior change
Comment From: wilkinsona
Thanks for the report. Looking at the output with --debug
, with Boot 3.5.4, the CycloneDX plugin incorrectly identifies several dependences as being direct:
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Traversal of graph for configuration productionRuntimeClasspath of project gh-46924
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Traversing node with ID root project :
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework.boot:spring-boot-starter-web:3.5.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework.boot:spring-boot-starter-web:3.5.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework.boot:spring-boot-starter:3.5.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework.boot:spring-boot-starter-json:3.5.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework.boot:spring-boot-starter-tomcat:3.5.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework:spring-web:6.2.9
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework:spring-webmvc:6.2.9
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework.boot:spring-boot:3.5.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework.boot:spring-boot-autoconfigure:3.5.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework.boot:spring-boot-starter-logging:3.5.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID jakarta.annotation:jakarta.annotation-api:2.1.1
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework:spring-core:6.2.9
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.yaml:snakeyaml:2.4
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID com.fasterxml.jackson.core:jackson-databind:2.19.2
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.19.2
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.19.2
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID com.fasterxml.jackson.module:jackson-module-parameter-names:2.19.2
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.apache.tomcat.embed:tomcat-embed-core:10.1.43
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.apache.tomcat.embed:tomcat-embed-el:10.1.43
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.apache.tomcat.embed:tomcat-embed-websocket:10.1.43
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework:spring-beans:6.2.9
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID io.micrometer:micrometer-observation:1.15.2
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework:spring-aop:6.2.9
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework:spring-context:6.2.9
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework:spring-expression:6.2.9
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID ch.qos.logback:logback-classic:1.5.18
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.apache.logging.log4j:log4j-to-slf4j:2.24.3
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.slf4j:jul-to-slf4j:2.0.17
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.springframework:spring-jcl:6.2.9
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID com.fasterxml.jackson.core:jackson-annotations:2.19.2
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID com.fasterxml.jackson.core:jackson-core:2.19.2
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID io.micrometer:micrometer-commons:1.15.2
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID ch.qos.logback:logback-core:1.5.18
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.slf4j:slf4j-api:2.0.17
2025-08-20T12:45:06.966+0100 [DEBUG] [org.gradle.api.Project] CycloneDX: Node with ID root project : has dependency with ID org.apache.logging.log4j:log4j-api:2.24.3
I suspect that this has been caused by the changes for https://github.com/spring-projects/spring-boot/issues/46043 where we now add constraints to that configuration. Rather than adding constraints explicitly, this has since been refined to use Gradle's shouldResolveConsistentlyWith
API. This change will be in 3.5.5 but unfortunately does not help based on your earlier testing of 3.5.5-20250820.075853-49
. That isn't surprising as I believe Gradle uses constraints in its implementation of the consistent resolution API.
Looking at the source of the CycloneDX Gradle plugin, the reason for the problem becomes apparent:
final ResolvedComponentResult dependencyComponent =
((ResolvedDependencyResult) dep).getSelected();
LOGGER.debug(
"CycloneDX: Node with ID {} has dependency with ID {}",
graphNode.id,
dependencyComponent);
final GraphNode dependencyNode = new GraphNode(dependencyComponent);
dependencyNode.inScopeConfiguration(projectName, configName);
graph.get(graphNode).add(dependencyNode);
queue.add(dependencyNode);
It makes no distinction between constraints and "real" dependencies. It can do so using DependencyResult#isConstraint. Please report this to the maintainers of the CycloneDX Gradle plugin.
In the meantime, you can work around the incorrect handling of constraints by configuring the bom generation to skip the productionRuntimeClasspath
configuration:
cyclonedxBom {
skipConfigs = ["productionRuntimeClasspath"]
}
Comment From: wilkinsona
Minimal example that reproduces the problem without involving Spring Boot:
plugins {
id 'java'
id 'org.cyclonedx.bom' version '2.3.1'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
constraints {
implementation 'org.springframework:spring-aop:6.2.10'
implementation 'org.springframework:spring-beans:6.2.10'
implementation 'org.springframework:spring-context:6.2.10'
implementation 'org.springframework:spring-core:6.2.10'
implementation 'org.springframework:spring-expression:6.2.10'
implementation 'org.springframework:spring-jcl:6.2.10'
implementation 'org.springframework:spring-web:6.2.10'
implementation 'org.springframework:spring-webmvc:6.2.10'
}
implementation 'org.springframework:spring-webmvc'
}
Comment From: tjog
Thanks for the quick investigation, redirection, and current workaround! 😄