当 Gradle 9、Docker 29 和 Testcontainers 自找麻烦时:调试 JHipster 构建
发布于 25 April 2026
- 场景:一个 JHipster 项目,一个不再加载的构建
- 错误 1 : 幻影变量 `reportsDir
- 错误 2:
main = \"…\"在 Gradle 9 中不再存在 - 错误 3 :
sourceCompatibility不再是项目的属性 - 错误4:Groovy 三元断言
- 错误 5:隐式依赖
compileKotlin→ `openApiGenerate - 错误 6 :
generateGitProperties在FilterOutputStream.write()上崩溃 - 最后:Testcontainers 登场
- 阶段 1 : docker-java 路径
- 阶段2:网页搜索和GitHub面包屑
- 第3阶段:被重命名的工件陷阱
- 阶段4:强制解决
- 最终结果
- 更正摘要表
- 结论
您曾经历过那个时刻,一个简单的`./gradlew build`您每天启动一百次的某个东西突然因前所未见的错误而崩溃?将其乘以十一次连续错误,加入足够的 Docker Engine 29 不兼容性,撒上 Gradle 9,它会让您十年来一直使用的 API 消失。以下是完整的日志。
目标:展示不兼容性系列。
@startuml left to right direction title Gradle 9 迁移 rectangle "1\n报告目录" as A rectangle "2\n主类" as B rectangle "3\n源兼容性" as C rectangle "4\nGroovy 断言" as D A --> B B --> C C --> D @enduml
目标:隔离损坏的插件。
@startuml left to right direction title 待修正的插件 skinparam shadowing false skinparam roundcorner 18 rectangle "1\nopenApiGenerate" as A #FFF3E0 rectangle "2\ngitProperties\n2.5.7" as B #FFF3E0 rectangle "3 个修正版本" as C #E3F2FD rectangle "4\n组装正常" as D #E8F5E9 A --> B B --> C C --> D @enduml
目标:运行时问题。
@startuml left to right direction title Docker 29 + Testcontainers skinparam shadowing false skinparam roundcorner 18 skinparam defaultFontSize 14 rectangle "1\n组装正常" as A #E8F5E9 rectangle "2\nCucumber 测试失败" as B #FFEBEE rectangle "3\nTestcontainers 1.32\n已拒绝" as C #FFF3E0 rectangle "4\n升级\n版本" as D #E3F2FD rectangle "5\n最终构建 OK" as E #E8F5E9 A --> B B --> C C --> D D --> E @enduml
@startuml !option handwritten true skinparam backgroundColor #FEFEFE skinparam shadowing false skinparam defaultFontName Inter skinparam defaultFontSize 14 skinparam ArrowThickness 2 skinparam RoundCorner 18 title 稳定化路线图 — 从 Gradle 9 到最终构建 left to right direction rectangle "⚙️ Gradle 9 迁移" as A #E3F2FD rectangle "🧩 JHipster 插件" as B #FFF3E0 rectangle "🐳 Docker 29\nTestcontainers" as C #FCE4EC rectangle "✅ 稳定构建" as D #E8F5E9 note bottom of A reportsDir mainClass sourceCompatibility Groovy assert end note note bottom of B openApiGenerate gitProperties versions alignées end note note bottom of C tests Cucumber KO image Docker rejetée upgrade libs end note A -[#42A5F5]-> B : corrections build.gradle B -[#FB8C00]-> C : assemble OK C -[#E53935]-> D : tests réparés @enduml
场景:一个 JHipster 项目,一个不再加载的构建
项目`edster`是一个在2024年生成的JHipster应用程序。 根构建脚本`build.gradle`它在几个月里保持稳定。然后决定升级版本:
| 组件 | 目标版本 |
|---|---|
Gradle |
9.4.1 |
Java |
21.0.11-tem (Eclipse Temurin) |
JHipster |
8.x (框架`tech.jhipster:jhipster-framework:8.11.0`) |
Spring Boot |
3.4.5 |
Kotlin |
2.3.0 |
Docker 引擎 |
29.4.1 (API 1.54) |
Java 版本的更改通过 SDKMAN 进行:
sdk use java 21.0.11-tem
首次发布:
$ ./gradlew build --no-daemon
FAILURE: Build failed with an exception.
* Where:
Build file '/home/.../edster/build.gradle' line: 18
* What went wrong:
An exception occurred applying plugin request [id: 'jhipster.cucumber-conventions']
> Failed to apply plugin 'jhipster.cucumber-conventions'.
> Could not create task ':cucumberTest'.
> Could not create task ':consoleLauncherTest'.
> Could not set unknown property 'reportsDir' for task ':consoleLauncherTest'
甚至连构建脚本的加载都没有开始。问题出在`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.
错误 1 : 幻影变量 `reportsDir
脚本`jhipster.cucumber-conventions.gradle`包含 :
tasks.register('consoleLauncherTest', JavaExec) {
dependsOn(testClasses)
String cucumberReportsDir = file("$buildDir/reports/tests")
outputs.dir(reportsDir) // <-- reportsDir n'existe PAS
classpath = sourceSets["test"].runtimeClasspath
main = "org.junit.platform.console.ConsoleLauncher"
// ...
}
定义的变量是`cucumberReportsDir`. 所使用的`reportsDir`-- 哪里都没有定义。
修正:`outputs.dir(cucumberReportsDir)`
错误 2:main = \"…\" 在 Gradle 9 中不再存在
立即跟进:
Could not set unknown property 'main' for task ':consoleLauncherTest' of type org.gradle.api.tasks.JavaExec.
Gradle 9 删除属性`main`为了…的利益`mainClass`在任务上`JavaExec`.
更正:`mainClass = "org.junit.platform.console.ConsoleLauncher"`
错误 3 : sourceCompatibility 不再是项目的属性
新错误:
Could not set unknown property 'sourceCompatibility' for root project 'edster' of type org.gradle.api.Project.
根脚本有:
sourceCompatibility=17
targetCompatibility=17
Gradle 9 删除这些根级别的属性。它们必须位于一个块中`java`.
修正:
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
错误4:Groovy 三元断言
旧脚本包含:
assert System.properties["java.specification.version"] == "17" || "21" || "24"
在Groovy中,"21" et "24"`是一些真值的字符串。这个表达式解析为(… == "17") || true || true`, 这总是正确的 — 但语法对于所期望的严格断言是无效的。
修正:
assert System.properties["java.specification.version"] in ["17", "21", "23", "24"]
错误 5:隐式依赖 compileKotlin → `openApiGenerate
构建进展顺利。Kotlin 编译成功。然后:
Task ':compileKotlin' uses this output of task ':openApiGenerate'
without declaring an explicit or implicit dependency.
Gradle 9 拒绝任务之间的隐式依赖。既然`compileKotlin`读取由生成的数据源`openApiGenerate`, 必须声明链接。
修正在`build.gradle`:
afterEvaluate {
tasks.named("compileKotlin").configure {
dependsOn(tasks.named("openApiGenerate"))
}
}
Le `afterEvaluate`是必要的因为`openApiGenerate`是一个扩展(OpenAPI Generator 插件),而不是在配置阶段直接可访问的任务。
错误 6 : generateGitProperties 在 FilterOutputStream.write() 上崩溃
Java 编译通过。资源已处理。然后:
> Task :generateGitProperties FAILED
No signature of method: java.io.FilterOutputStream.write() is applicable for argument types: (Integer) values: [103]
插件`gradle-git-properties`version 2.5.0 与 Java 21 / Gradle 9 不兼容。内部错误尝试调用`write(int)`通过 Groovy 在一个它已关闭的流上。
更正在`gradle.properties` :
gitPropertiesPluginVersion=2.5.7
最后:Testcontainers 登场
到目前为止,这纯粹是 "build script debugging"。每个错误都是构建脚本中 Gradle 9 或 Java 21 的不兼容性。在以上六个修正之后,`./gradlew assemble`顺利通过。
但是`./gradlew build`也执行测试,并且 Cucumber 测试使用 Testcontainers 启动一个临时的 PostgreSQL。
Could not find a valid Docker environment.
EnvironmentAndSystemPropertyClientProviderStrategy: failed with exception BadRequestException
(Status 400: {"message":"client version 1.32 is too old. Minimum supported API version is 1.40"}
UnixSocketClientProviderStrategy: failed with exception BadRequestException
(Status 400: {"message":"client version 1.32 is too old. Minimum supported API version is 1.40"}
Testcontainers 无法与 Docker 通信。两种不同的策略(环境变量 + Unix 套接字)在同一个错误上失败。
@startuml skinparam backgroundColor #FEFEFE skinparam handwritten false actor "Testcontainers\n1.20.6" as TC participant "docker-java\n3.4.1" as DJ participant "Docker Engine\n29.4.1" as DE TC -> DJ : new DockerClient() DJ -> DE : GET /_ping\nUser-Agent: docker-java/3.4.1\nAPI-Version: 1.32 DE --> DJ : HTTP 400 错误请求\n客户端版本 1.32 太旧。\n最低支持的 API 版本是 1.40 DJ --> TC : BadRequestException TC --> TC : 找不到有效的\nDocker 环境 note right of DE Docker Engine 29.x 强制 API 最小值 = 1.40 (旧默认值: 1.24) end note note left of DJ docker-java 3.4.1 发送 硬编码 API 版本 1.32 在头部 HTTP end note @enduml
阶段 1 : docker-java 路径
第一个反射:Testcontainers 使用的 Java Docker 客户端发送 API 版本`1.32`在其 HTTP 握手中,但 Docker Engine 29.4.1 需要至少`1.40`. 问题在于客户端,而不是守护进程。
Testcontainers 1.20.6 解析的 docker-java 版本验证:
$ ./gradlew dependencies --configuration testRuntimeClasspath | grep docker-java
+--- com.github.docker-java:docker-java-api:3.4.1
+--- com.github.docker-java:docker-java-transport:3.4.1
docker-java 3.4.1,源自 Testcontainers 1.20.6。docker-java 的最新公开版本是 3.5.1。让我们尝试通过强制升级版本`resolutionStrategy`在块中`configurations` de build.gradle</think>
configurations {
all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == "com.github.docker-java"
&& details.requested.name.startsWith("docker-java")) {
details.useVersion("3.5.1")
}
}
}
}
重新启动构建。同样的错误:client version 1.32 is too old.
Gradle缓存验证:JARs已重新下载,但错误仍然存在。docker-java 3.5.1仍在发送`1.32`. 此版本因此不足以满足 Docker Engine 29.4.1 的需求。
净化行动: 完全清空 Gradle 缓存,只是为了确保 :
rm -rf ~/.gradle/caches
rm -rf /path/to/project/.gradle
清除后完整重启。同样的错误。
阶段2:网页搜索和GitHub面包屑
docker-java 3.5.1 不足。只剩下寻找是否有人遇到过这个确切的问题。
针对 testcontainers-java 和 docker-java 的议题进行有针对性的搜索:
site:github.com/testcontainers "client version 1.32 is too old"
site:github.com/docker-java "client version 1.32" Docker Engine 29
初步的即时结果 :
-
testcontainers-java#11210—
[Bug]: client version 1.32 is too old. Minimum supported API version is 1.44 -
testcontainers-java#11212—
[Bug]: Docker 29.0.0 could not find a valid Docker environment -
testcontainers-java#11491—
[Bug]: Incompatibility in Ubuntu Github runners with Docker Engine 29.1.*
面包屑导航很清楚:Docker Engine 29.x 已将其最低 API 版本提高到`1.24`(旧缺陷) 到`1.40`. Testcontainers 1.20.x 使用 docker-java 3.4.x 它通过发送来协商 API 版本`1.32`. 守护进程礼貌但坚定地拒绝。
在议题 #11491中,一位维护者回复:
</think> 嗨,我在 GH runner 上使用引擎 29.1 运行版本 2.0.3 的项目,并且它能够正常工作。 请大家检查一下是否没有版本冲突? 请记得所有模块前面都带有`testcontainers-。 那么,从2.x版本开始,我们从`postgresql to testcontainers-postgresql __
此回答包含两条关键信息</think> :
-
Testcontainers 2.0.3+ 解决了问题
-
模块已被使用前缀
testcontainers-重命名
第3阶段:被重命名的工件陷阱
重命名非常残酷。在 Testcontainers 2.x 中,旧名称在该名称下均不起作用:
| 旧名称 (1.x) | 新名称 (2.x) |
|---|---|
|
|
|
|
|
|
|
未更改 |
第一次尝试:应用 Testcontainers 2.0.5 BOM 和新的工件名称 在`build.gradle`.
dependencies {
testImplementation platform("org.testcontainers:testcontainers-bom:2.0.5")
testImplementation "org.testcontainers:testcontainers-postgresql"
testImplementation "org.testcontainers:testcontainers-jdbc"
testImplementation "org.testcontainers:testcontainers-junit-jupiter"
testImplementation "org.testcontainers:testcontainers"
}
./gradlew build
FAILURE: Could not find org.testcontainers:jdbc:2.0.5.
Could not find org.testcontainers:junit-jupiter:2.0.5.
testcontainers-bom的BOM不够。为什么?因为 Spring Boot 3.4.5 暴露了它自己的依赖管理 BOM(spring-boot-dependencies) 谁赢在 Testcontainers 的 BOM 中,在传递解析中。Spring Boot 修复`testcontainers` à 1.20.6, 因此所有未显式包含在Spring Boot BOM中的artifact(如新的`testcontainers-*) 解析到旧名称`1.20.6-- 在这一版本中不再存在。
在检查`dependencies --configuration testRuntimeClasspath`, 有 :
+--- org.testcontainers:testcontainers -> 1.20.6 (*)
| \--- org.testcontainers:testcontainers:2.0.5 -> 1.20.6
Le →`表示替换:Testcontainers 2.0.5 是 需求,但 Spring Boot 强制`1.20.6.
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle\n(消费者)" as BG #E3F2FD {
rectangle "请求\n`testcontainers-bom:2.0.5`" as REQ
rectangle "请求\n`testcontainers-postgresql`" as REQ2
}
component "Maven 中心" as MC #FFF3E0
component "声明" as DEC #E8F5E9 {
rectangle "`testcontainers-bom:2.0.5`\n固定 2.0.5" as BOM2
rectangle "`spring-boot-dependencies:3.4.5`\n固定 1.20.6" as BOM1
}
component "解决 Gradle" as RES #FFEBEE {
rectangle "获胜版本\n`testcontainers:1.20.6`" as WIN #EF9A9A
}
BG --> DEC : "分析 BOMs"
BOM2 --> RES : "建议 2.0.5"
BOM1 --> RES : "建议 1.20.6"
WIN -> MC : "下载\ntestcontainers:1.20.6"
note bottom of WIN
Spring Boot BOM 主要因为
spring-boot 插件会应用
其管理依赖
在消费者解析之后
end note
@enduml
阶段4:强制解决
战略变得双重:
-
强制 docker-java 使用版本 3.7.1(已测试与 Docker Engine 29 兼容)
-
将 Testcontainers core 强制设置为 2.0.5,通过绕过 Spring Boot BOM
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle\n(带有 resolutionStrategy)" as BG #E8F5E9 {
rectangle "BOM Testcontainers\n`2.0.5`" as BOM2
rectangle "`resolutionStrategy\n.eachDependency`" as RS #66BB6A
}
component "冲突已解决" as RESOLVED #E3F2FD {
rectangle "docker-java\n`3.7.1` (强制)" as DJ
rectangle "testcontainers\n`2.0.5` (强制)" as TC #81C784
rectangle "testcontainers-postgresql\n`2.0.5`" as TPG
rectangle "testcontainers-jdbc\n`2.0.5`" as TJ
rectangle "testcontainers-junit-jupiter\n`2.0.5`" as TJJ
}
component "BOM Spring Boot\n(隐式, 被覆盖)" as BOM1 #FFEBEE
component "Maven Central" as MC #FFF3E0
BG --> RESOLVED : "BOM Testcontainers 2.0.5\n直接通过"
RS -> DJ : "强制 3.7.1"
RS -> TC : "强制 2.0.5"
RESOLVED --> MC : "下载 JARs\n兼容的 Docker 29"
BOM1 -[#gray,dashed]-> RESOLVED : "已忽略在 testcontainers\n和 docker-java"
note bottom of RS
`eachDependency` 拦截了 Gradle AVANT 解析,BOM Spring Boot 无法获得。这是一项外科手术。
end note
@enduml
最终校正在`build.gradle`:
configurations {
all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == "com.github.docker-java"
&& details.requested.name.startsWith("docker-java")) {
details.useVersion("3.7.1")
}
if (details.requested.group == "org.testcontainers"
&& details.requested.name == "testcontainers") {
details.useVersion("2.0.5")
}
}
}
}
dependencies {
testImplementation platform("org.testcontainers:testcontainers-bom:2.0.5")
testImplementation "org.testcontainers:testcontainers-jdbc"
testImplementation "org.testcontainers:testcontainers-junit-jupiter"
testImplementation "org.testcontainers:testcontainers-postgresql"
testImplementation "org.testcontainers:testcontainers"
// ... autres dépendances Spring Boot
}
Le `resolutionStrategy.eachDependency`这是绕过 BOM 的唯一方法`spring-boot-dependencies`由 Spring Boot 插件管理。关于相等性测试的`details.requested.name == "testcontainers"`这是有意的限制:我们只强制核心模块。模块`testcontainers-*`它们遵循我们显式声明的 Testcontainers 2.0.5 BOM。
最终结果
$ ./gradlew build --no-daemon
> Task :consoleLauncherTest
[2 containers found]
[2 containers started]
[2 containers successful]
BUILD SUCCESSFUL in 1m 16s
Testcontainers 2.0.5 + docker-java 3.7.1 终于能够通过 Docker Engine 29.4.1 创建他们的 PostgreSQL 容器。最终的业务错误(Cucumber 测试上的 HTTP 500)是一个与构建脚本完全无关的纯粹的应用问题。
更正摘要表
| # | 文件 | 问题 | 修正 |
|---|---|---|---|
1 |
|
变量`reportsDir`不存在的 |
|
2 |
|
`main`已弃用 Gradle 9 |
|
3 |
|
`sourceCompatibility`顶级禁止 Gradle 9 |
块`java { sourceCompatibility = JavaVersion.VERSION_21 }` |
4 |
|
Groovy 断言语法无效 |
|
5 |
|
隐式依赖`compileKotlin` → |
|
6 |
|
`gitPropertiesPluginVersion`不兼容 Java 21 |
|
7 |
缓存 Gradle |
docker-java 3.5.1 已测试,已解决但不足 |
清除 + 通过 docker-java 3.7.1 |
8 |
|
Testcontainers 1.20.6 不兼容 Docker Engine 29 |
强制 Testcontainers 2.0.5 + 前缀`testcontainers-*`+ resolutionStrategy 与 Spring Boot BOM |
结论
如果您的 JHipster/Gradle 构建在升级到 Gradle 9 + Docker Engine 29 后崩溃,错误将分为两类:
构建脚本错误(前6个) : Gradle 9 删除了多年有效的属性和语法`sourceCompatibility=, `main =, reportsDir).这些是机械迁移,一旦我们知道新的API。
Docker运行时错误 (les 2 dernières) : Docker Engine 29.4.1 écarte les clients API < 1.40. Testcontainers 1.20.6 (imposé par Spring Boot 3.4.5) utilise docker-java 3.4.1 qui envoie 1.32. Seul Testcontainers 2.0.5 + docker-java 3.7.1 résout le problème, avec le renommage des artifacts et le forçage de version via`resolutionStrategy.eachDependency`作为绕过 Spring Boot 隐含 BOM 的唯一方法。
项目根目录下的 Gradle 构建脚本现在很干净。`./gradlew build`基于 Java 21、Gradle 9.4.1、Spring Boot 3.4.5 和 Docker Engine 29.4.1。
相关文章
31 May 2026
14 May 2026
12 May 2026