Quand Gradle 9, Docker 29 et Testcontainers se mettent la rate au court-bouillon : debugging d'un build 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\nreportsDir" as A rectangle "2\nmainClass" as B rectangle "3\nsourceCompatibility" as C rectangle "4\nGroovy assert" 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\nVersions\nисправлены" 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Сборка OK" as A #E8F5E9 rectangle "2\nТесты Cucumber FAIL" 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 выровненные версии end note note bottom of C тесты Cucumber KO образ Docker отклонен обновление библиотек end note A -[#42A5F5]-> B : corrections build.gradle B -[#FB8C00]-> C : assemble OK C -[#E53935]-> D : tests réparés @enduml
Сцена: проект JHipster, сборка, которая больше не загружается
Проект`edster`это приложение JHipster, сгенерированное в 2024 году. Корневой скрипт сборки`build.gradle`Он оставался стабильным в течение месяцев. Затем решили обновить до версии:
| Компонент | Целевая версия |
|---|---|
Gradle |
9.4.1 |
Джава |
21.0.11-tem (Eclipse Temurin) |
JHipster |
8.x (фреймворк`tech.jhipster:jhipster-framework:8.11.0`) |
Spring Boot |
3.4.5 |
Котлин |
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"`являются strings truthy. Выражение разрешается в(… == "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`версия 2.5.0 несовместима с Java 21 / Gradle 9. Внутренняя ошибка пытается вызвать`write(int)`через Groovy на потоке, который он закрыл.
Исправлениев`gradle.properties`:
gitPropertiesPluginVersion=2.5.7
Наконец: Testcontainers вступает в игру
До этого это была чистая отладка скриптов сборки. Каждая ошибка была несовместимостью 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 Bad Request\nclient version 1.32 is too old.\nMinimum supported API version is 1.40 DJ --> TC : BadRequestException TC --> TC : Could not find a valid\nDocker environment note right of DE Docker Engine 29.x impose API minimum = 1.40 (défaut ancien : 1.24) end note note left of DJ docker-java 3.4.1 envoie hardcoded API version 1.32 dans l'en-tête HTTP end note @enduml
Этап 1: путь docker-java
Первый рефлекс: клиент Docker Java, используемый Testcontainers, отправляет версию API`1.32`в его HTTP-handshake, но Docker Engine 29.4.1 требует минимум`1.40`. Проблема находится на уровне клиента, а не демона.
Проверка версии docker-java, решённая Testcontainers 1.20.6 :
$ ./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 :
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: JAR‑файлы успешно перезагружены, но ошибка сохраняется. 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 maintainer отвечает:
_ Привет, у меня запущен проект с версией 2.0.3 на GH runner с движком 29.1, и он работает. Пожалуйста, проверьте, нет ли конфликта версий? Пожалуйста, запомните, что все модули были префиксированы с`testcontainers-. Итак, начиная с версии 2.x мы перешли от`postgresql to testcontainers-postgresql _
Этот ответ содержитдве критические сведения :
-
Testcontainers 2.0.3+ решает проблему
-
Модули былипереименованные с префиксом `testcontainers-
Фаза 3: ловушка переименованных артефактов
Переименование жестоко. В Testcontainers 2.x ни одно из старых имён не работает под этим именем:
| Старое название (1.x) | Новое имя (2.x) |
|---|---|
|
|
|
|
|
|
|
неизменный |
Первая попытка: применить BOM Testcontainers 2.0.5 и новые названия артефактов в`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.
BOM testcontainers-bom недостаточно.Почему? Потому что Spring Boot 3.4.5 предоставляет свой собственный BOM управления зависимостями (spring-boot-dependencies) которыйвыиграйВ транзитивном разрешении BOM Testcontainers. Spring Boot устанавливает`testcontainers` à 1.20.6, и поэтому все артефакты, которые не указаны явно в BOM Spring Boot (как новые`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(consommateur)" as BG #E3F2FD {
rectangle "demande\n`testcontainers-bom:2.0.5`" as REQ
rectangle "demande\n`testcontainers-postgresql`" as REQ2
}
component "Maven Central" as MC #FFF3E0
component "Déclarations" as DEC #E8F5E9 {
rectangle "`testcontainers-bom:2.0.5`\nfixe 2.0.5" as BOM2
rectangle "`spring-boot-dependencies:3.4.5`\nfixe 1.20.6" as BOM1
}
component "Résolution Gradle" as RES #FFEBEE {
rectangle "Version gagnante\n`testcontainers:1.20.6`" as WIN #EF9A9A
}
BG --> DEC : "analyse les BOMs"
BOM2 --> RES : "propose 2.0.5"
BOM1 --> RES : "propose 1.20.6"
WIN -> MC : "télécharge\ntestcontainers:1.20.6"
note bottom of WIN
Spring Boot BOM prime car
le plugin spring-boot applique
ses dépendances de gestion
après la résolution du consommateur
end note
@enduml
Этап 4 : принудительно разрешить
Стратегия становится двойной:
-
Принудительно установить docker-java в версию 3.7.1 (протестирована как совместимая с Docker Engine 29)
-
Принудительно установить Testcontainers core версии 2.0.5, обходя BOM Spring Boot
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle\n(avec resolutionStrategy)" as BG #E8F5E9 {
rectangle "BOM Testcontainers\n`2.0.5`" as BOM2
rectangle "`resolutionStrategy\n.eachDependency`" as RS #66BB6A
}
component "Conflit résolu" as RESOLVED #E3F2FD {
rectangle "docker-java\n`3.7.1` (forcé)" as DJ
rectangle "testcontainers\n`2.0.5` (forcé)" 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(implicite, outrepassé)" as BOM1 #FFEBEE
component "Maven Central" as MC #FFF3E0
BG --> RESOLVED : "BOM Testcontainers 2.0.5\npasse directement"
RS -> DJ : "force 3.7.1"
RS -> TC : "force 2.0.5"
RESOLVED --> MC : "télécharge les JARs\ncompatibles Docker 29"
BOM1 -[#gray,dashed]-> RESOLVED : "ignoré sur testcontainers\net docker-java"
note bottom of RS
Le `eachDependency` intercepte
la résolution Gradle AVANT
que le BOM Spring Boot ne gagne.
C'est une intervention chirurgicale.
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-*`следуют BOM Testcontainers 2.0.5, который мы явно объявили.
Итоговый результат
$ ./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 наконец смогли создать свои контейнеры PostgreSQL через Docker Engine 29.4.1. Последняя бизнес‑ошибка (HTTP 500 в тесте Cucumber) — это полностью независимая от скрипта сборки проблема приложения.
Сводная таблица исправлений
| # | Файл | проблема | Исправление |
|---|---|---|---|
1 |
|
Переменная`reportsDir`несуществующая |
|
2 |
|
`main`устаревший Gradle 9 |
|
3 |
|
`sourceCompatibility`top-level запрещено 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(последние 2) : Docker Engine 29.4.1 исключает клиенты API < 1.40. Testcontainers 1.20.6 (навязанный Spring Boot 3.4.5) использует docker-java 3.4.1, который отправляет 1.32. Только Testcontainers 2.0.5 + docker-java 3.7.1 решает проблему, с переименованием артефактов и принудительным указанием версии via`resolutionStrategy.eachDependency`как единственный способ обойти неявный BOM Spring Boot.
Скрипт сборки Gradle в корне проекта теперь чистый.`./gradlew build`переходит на Java 21, Gradle 9.4.1, Spring Boot 3.4.5 и Docker Engine 29.4.1.