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! 😄