Repository: GradleUp/shadow (kotlin)

Issue:
NPE in UnusedTracker with Gradle 9 when minimize() meets platform/BOM or api dependencies
### Expected and Results

After upgrading to Gradle 9.0.0, running shadowJar with minimize() fails with a NullPointerException inside UnusedTracker.getApiJarsFromProject. This happens when the project has non-file dependencies on the api configuration (e.g., platform/BOM) that the minimizer attempts to inspect.

The plugin should gracefully skip such entries (or resolve them differently) instead of casting null to File.

Failing code path: https://github.com/GradleUp/shadow/blob/main/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/UnusedTracker.kt

Actual behavior
A NullPointerException is thrown when resolving the dependencies of the shadowJar task:
```
Caused by: java.lang.NullPointerException: null cannot be cast to non-null type java.io.File
at com.github.jengelman.gradle.plugins.shadow.internal.UnusedTracker$Companion.getApiJarsFromProject(UnusedTracker.kt:59)
at com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.apiJars$lambda$9$lambda$7(ShadowJar.kt:102)
...
```

### Related environment and versions

- Gradle: 9.0.0
- Shadow plugin: 9.0.1 (ID: com.gradleup.shadow)
- Java: 17
- Kotlin: 2.2.0
- OS: Linux

### Reproduction steps

1) settings.gradle.kts (two-module build)
```kotlin
rootProject.name = "shadow-npe-repro"
include(":app")
```

2) Root build.gradle.kts (apply a platform/BOM on api in subprojects)
```kotlin
plugins {
    kotlin("jvm") version "2.2.0" apply false
}

subprojects {
    apply(plugin = "org.jetbrains.kotlin.jvm")

    repositories {
        mavenCentral()
    }

    dependencies {
        // This causes the crash when minimize() runs on Gradle 9
        add("api", platform("org.jetbrains.kotlin:kotlin-bom:2.2.0"))
        add("implementation", kotlin("stdlib"))
    }
}
```

3) app/build.gradle.kts (enable shadow with minimize)
```kotlin
plugins {
    id("com.gradleup.shadow") version "9.0.1"
    kotlin("jvm")
}

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))
}

tasks.shadowJar {
    minimize()
}
```

4) Run:
./gradlew :app:shadowJar --stacktrace

Result: NPE at UnusedTracker.getApiJarsFromProject.



### Anything else?

- This appears after upgrading to Gradle 9; it did not reproduce in our setup before the Gradle 9 upgrade.
- The compatibility matrix indicates Shadow 9.x supports Gradle ≥ 8.11 and Java ≥ 11, so this configuration should be supported.

Workarounds
- Disable minimize() for the affected module.
- Use implementation instead of api for platform/BOM and most dependencies in CLI/app modules so they are not exposed via api.
- Alternatively, exclude BOMs from minimization:
```kotlin
tasks.shadowJar {
    minimize {
        exclude(dependency { d ->
            d.moduleName.endsWith("-bom") || d.moduleName == "kotlin-bom"
        })
    }
}
```

Pull request description:
Closes #1636.

---

- [x] [CHANGELOG](https://github.com/GradleUp/shadow/blob/main/docs/changes/README.md)'s "Unreleased" section has been updated, if applicable.

Patch:
diff --git a/docs/changes/README.md b/docs/changes/README.md
index 85a81a9b0..7a57af558 100644
--- a/docs/changes/README.md
+++ b/docs/changes/README.md
@@ -8,6 +8,7 @@
 - Fix missing space in `ApacheNoticeResourceTransformer` preamble causing malformed NOTICE header. ([#1623](https://github.com/GradleUp/shadow/pull/1623)).
 - Fix using `ApacheNoticeResourceTransformer` without `projectName`. ([#1627](https://github.com/GradleUp/shadow/pull/1627)).
 - Fix extra indents of `ApacheNoticeResourceTransformer` output. ([#1628](https://github.com/GradleUp/shadow/pull/1628)).
+- Fix resolving BOM dependencies when `minimize` is enabled. ([#1637](https://github.com/GradleUp/shadow/pull/1637)).
 
 ## [9.0.1](https://github.com/GradleUp/shadow/releases/tag/9.0.1) - 2025-08-09
 
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/UnusedTracker.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/UnusedTracker.kt
index b82aaeda7..1c6f9d97a 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/UnusedTracker.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/UnusedTracker.kt
@@ -4,6 +4,7 @@ import java.io.File
 import org.gradle.api.Project
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ExternalModuleDependency
 import org.gradle.api.artifacts.FileCollectionDependency
 import org.gradle.api.artifacts.ProjectDependency
 import org.gradle.api.file.FileCollection
@@ -54,9 +55,12 @@ internal class UnusedTracker(
           is FileCollectionDependency -> {
             apiJars.addAll(dep.files)
           }
+          // Skip BOM dependencies and other non-JAR dependencies.
+          is ExternalModuleDependency -> Unit
           else -> {
             addJar(runtimeConfiguration, dep, apiJars)
-            apiJars.add(runtimeConfiguration.find { it.name.startsWith("${dep.name}-") } as File)
+            val jarFile = runtimeConfiguration.find { it.name.startsWith("${dep.name}-") } ?: return@forEach
+            apiJars.add(jarFile)
           }
         }
       }


Test patch:
diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/MinimizeTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/MinimizeTest.kt
index 4bbb9456c..897dacd4d 100644
--- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/MinimizeTest.kt
+++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/MinimizeTest.kt
@@ -279,6 +279,36 @@ class MinimizeTest : BasePluginTest() {
     }
   }
 
+  @Issue(
+    "https://github.com/GradleUp/shadow/issues/1636",
+  )
+  @Test
+  fun minimizeBomDependency() {
+    writeApiLibAndImplModules()
+    path("impl/build.gradle").appendText(
+      """
+        dependencies {
+          api platform('org.jetbrains.kotlin:kotlin-bom:2.2.0')
+        }
+      """.trimIndent(),
+    )
+
+    run(":impl:$SHADOW_JAR_TASK_NAME")
+
+    assertThat(jarPath("impl/build/libs/impl-all.jar")).useAll {
+      containsAtLeast(
+        "api/",
+        "lib/",
+        "impl/",
+        "impl/SimpleEntity.class",
+        "api/Entity.class",
+        "api/UnusedEntity.class",
+        "lib/LibEntity.class",
+        *manifestEntries,
+      )
+    }
+  }
+
   private fun writeApiLibAndImplModules() {
     settingsScript.appendText(
       """