Wanneer Gradle 9, Docker 29 en Testcontainers de moed verliezen: debuggen van een JHipster-build
Gepubliceerd op 25 April 2026
- De scène: een JHipster-project, een build die niet meer laadt
- Fout 1 : de fantomvariabele `reportsDir
- Fout 2:
main = "…"bestaat niet meer onder Gradle 9 - Fout 3:
sourceCompatibilityis geen projecteigenschap meer. - Fout 4 : de drie-operanden Groovy-assertie
- Fout 5: de impliciete afhankelijkheid
compileKotlin→ `openApiGenerate - Fout 6 :
generateGitPropertiesbreekt op `FilterOutputStream.write() - Ten slotte: Testcontainers betreedt het toneel
- Fase 1: het docker-java pad
- Fase 2 : webzoektocht en breadcrumb GitHub
- Fase 3: de val van hernoemde artefacten
- Fase 4: de oplossing afdwingen
- Eindresultaat
- Overzichtstabel van de correcties
- Conclusie
Je hebt dit moment al meegemaakt waarop een eenvoudig`./gradlew build`die je honderd keer per dag uitvoert, gaat plotseling crashen bij een fout die nog nooit gezien is? Vermenigvuldig dit met elf opeenvolgende fouten, voeg een goede dosis incompatibiliteit van Docker Engine 29 toe, bestrooi met Gradle 9, dat API’s laat verdwijnen die we tien jaar lang gebruikten. Hier is het volledige logboek.
Doel: de reeks onverenigbaarheden tonen.
@startuml left to right direction title Migratie 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
Doel: de defecte plugins isoleren.
@startuml left to right direction title Plugins om te corrigeren skinparam shadowing false skinparam roundcorner 18 rectangle "1\nopenApiGenerate" as A #FFF3E0 rectangle "2\ngitProperties\n2.5.7" as B #FFF3E0 rectangle "3\nVersies\ngecorrigeerd" as C #E3F2FD rectangle "4\nAssemble OK" as D #E8F5E9 A --> B B --> C C --> D @enduml
Doel: runtime-probleem
@startuml left to right direction title Docker 29 + Testcontainers skinparam shadowing false skinparam roundcorner 18 skinparam defaultFontSize 14 rectangle "1\nAssemble OK" as A #E8F5E9 rectangle "2\nCucumber Tests GEFAALD" as B #FFEBEE rectangle "3\nTestcontainers 1.32\ngeweigerd" as C #FFF3E0 rectangle "4\nUpgrade\nversies" as D #E3F2FD rectangle "5\nFinale build 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 Stabiliseringsroadmap — Gradle 9 naar Finale Build left to right direction rectangle "⚙️ Migratie Gradle 9" as A #E3F2FD rectangle "🧩 Plugins JHipster" as B #FFF3E0 rectangle "🐳 Docker 29\nTestcontainers" as C #FCE4EC rectangle "✅ Build Stable" as D #E8F5E9 note bottom of A reportsDir mainClass sourceCompatibility Groovy assert end note note bottom of B openApiGenerate gitProperties uitgelijnde versies end note note bottom of C Cucumber tests KO Docker image geweigerd libs upgrade end note A -[#42A5F5]-> B : corrections build.gradle B -[#FB8C00]-> C : assemble OK C -[#E53935]-> D : tests réparés @enduml
De scène: een JHipster-project, een build die niet meer laadt
Het project`edster`is a JHipster application generated in 2024. The root build script`build.gradle`is maandenlang stabiel gebleven. Daarna besluiten we om de versie te verhogen:
| Component | Doelversie |
|---|---|
Gradle |
9.4.1 |
Java |
21.0.11-tem (Eclipse Temurin) |
JHipster |
8.x (framework`tech.jhipster:jhipster-framework:8.11.0`) |
Spring Boot |
3.4.5 |
Kotlin |
2.3.0 |
Docker Engine |
29.4.1 (API 1.54) |
Het wijzigen van de Java-versie gaat via SDKMAN:
sdk use java 21.0.11-tem
Eerste lancering:
$ ./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'
We komen zelfs niet verder dan het laden van het build-script. Het probleem zit in`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.
Fout 1 : de fantomvariabele `reportsDir
Het script`jhipster.cucumber-conventions.gradle`bevat :
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"
// ...
}
De gedefinieerde variabele is`cucumberReportsDir`. Die gebruikt is`reportsDir`-- die nergens is gedefinieerd.
Correctie:`outputs.dir(cucumberReportsDir)`
Fout 2: main = "…" bestaat niet meer onder Gradle 9
Onmiddellijke herlancering :
Could not set unknown property 'main' for task ':consoleLauncherTest' of type org.gradle.api.tasks.JavaExec.
Gradle 9 verwijdert de eigenschap`main`in het voordeel van`mainClass`over de taken`JavaExec`.
Correctie:`mainClass = "org.junit.platform.console.ConsoleLauncher"`
Fout 3: sourceCompatibility is geen projecteigenschap meer.
Nieuwe fout :
Could not set unknown property 'sourceCompatibility' for root project 'edster' of type org.gradle.api.Project.
Het hoofdscript had:
sourceCompatibility=17
targetCompatibility=17
Gradle 9 verwijdert deze eigenschappen op hoofdniveau. Ze moeten in een blok worden geplaatst.java.
Correctie :
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
Fout 4 : de drie-operanden Groovy-assertie
Het oude script bevatte:
assert System.properties["java.specification.version"] == "17" || "21" || "24"
In Groovy`"21"` et "24"`zijn truthy strings. De expressie lost op in(… == "17") || true || true`, wat altijd waar is — maar de syntax is ongeldig voor de gewenste strikte assertie.
Correctie:
assert System.properties["java.specification.version"] in ["17", "21", "23", "24"]
Fout 5: de impliciete afhankelijkheid compileKotlin → `openApiGenerate
De build vordert. Kotlin-compilatie geslaagd. Dan :
Task ':compileKotlin' uses this output of task ':openApiGenerate'
without declaring an explicit or implicit dependency.
Gradle 9 weigert impliciete afhankelijkheden tussen taken. Aangezien`compileKotlin`leest de door … gegenereerde bronnen`openApiGenerate`, men moet de link aangeven.
Correctiein`build.gradle`:
afterEvaluate {
tasks.named("compileKotlin").configure {
dependsOn(tasks.named("openApiGenerate"))
}
}
Le `afterEvaluate`is nodig omdat`openApiGenerate`is een extensie (OpenAPI Generator-plugin) en geen taak die direct toegankelijk is tijdens de configuratiefase.
Fout 6 : generateGitProperties breekt op `FilterOutputStream.write()
De Java-compilatie slaagt. De bronnen worden verwerkt. Vervolgens:
> Task :generateGitProperties FAILED
No signature of method: java.io.FilterOutputStream.write() is applicable for argument types: (Integer) values: [103]
De plugin`gradle-git-properties`versie 2.5.0 is incompatibel met Java 21 / Gradle 9. Een interne bug probeert aan te roepen`write(int)`via Groovy op een stroom die het gesloten heeft.
correctiein`gradle.properties`(empt)
gitPropertiesPluginVersion=2.5.7
Ten slotte: Testcontainers betreedt het toneel
Tot nu toe was dit pure "build script debugging". Elke fout was een incompatibiliteit met Gradle 9 of Java 21 in de buildscripts. Na de zes correcties hierboven, de`./gradlew assemble`slaagt
maar`./gradlew build`Voert ook de tests uit, en de Cucumber-tests gebruiken Testcontainers om een tijdelijke PostgreSQL te starten.
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 kan niet communiceren met Docker. Twee verschillende strategieën (omgevingsvariabelen + Unix-socket) falen bij dezelfde fout.
@startuml skinparam backgroundColor #FEFEFE skinparam handwritten false actor "Testcontainers\n1.20.6" as TC participant "docker-java\n3.4.1" as DJ participant "Docker motor\n29.4.1" as DE TC -> DJ : nieuw DockerClient() DJ -> DE : GET /_ping\nUser-Agent: docker-java/3.4.1\nAPI-Version: 1.32 DE --> DJ : HTTP 400 Ongeldig verzoek\nclientversie 1.32 is te oud.\nMinimaal ondersteunde API versie is 1.40 DJ --> TC : BadRequestException TC --> TC : Kon geen geldige\nDocker omgeving vinden note right of DE Docker Motor 29.x oplegt API minimum = 1.40 (oude standaard: 1.24) end note note left of DJ docker-java 3.4.1 verstuurt hardgecodeerd API versie 1.32 in de header HTTP end note @enduml
Fase 1: het docker-java pad
Eerste reflex : de Java Docker-client die Testcontainers gebruikt, stuurt de API-versie`1.32`in zijn HTTP-handshake, maar Docker Engine 29.4.1 vereist een minimum van`1.40`. Het probleem is aan de clientzijde, niet bij de daemon.
Verificatie van de docker-java versie opgelost door 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, daterend van Testcontainers 1.20.6. De nieuwste publieke versie van docker-java is 3.5.1. Laten we proberen de versie-upgrade te forceren via`resolutionStrategy`in het blok`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")
}
}
}
}
Opnieuw bouwen starten. Zelfde fout:`client version 1.32 is too old`.
Controle van de Gradle-cache: de JARs zijn correct opnieuw gedownload, maar de fout blijft bestaan. docker-java 3.5.1 verzendt nog steeds`1.32`. Deze versie is dus NIET voldoende voor Docker Engine 29.4.1.
actie van zuivering: Volledig de Gradle-cache wissen, alleen maar om zeker te zijn :
rm -rf ~/.gradle/caches
rm -rf /path/to/project/.gradle
Volledige herstart na opschonen. Dezelfde fout.
Fase 2 : webzoektocht en breadcrumb GitHub
docker-java 3.5.1 is onvoldoende. Er blijft alleen over om te zoeken of iemand dit exacte probleem al heeft meegemaakt.
Gerichte zoekopdracht op de issues testcontainers-java en 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
Eerste directe resultaten :
-
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.*
De Ariadendraad is duidelijk : Docker Engine 29.x heeft zijn minimale API-versie verhoogd tot`1.24`(oud defect) aan`1.40`. Testcontainers 1.20.x maakt gebruik van docker-java 3.4.x dat de API-versie onderhandelt door te verzenden`1.32`. De daemon weigert beleefd maar vastberaden.
In issue #11491 reageert een maintainer:
(empty) Hoi, ik heb een project dat draait met versie 2.0.3 op een GH runner met engine 29.1 en het werkt. Kunnen jullie alsjeblieft controleren of er geen versieconflict is? Alsjeblieft, onthoud dat alle modules waren voorzien van een voorvoegsel`testcontainers-. Dus, vanaf versie 2.x gingen we van`postgresql to testcontainers-postgresql __
Dit antwoord bevattwee kritieke informatie :
-
Testcontainers 2.0.3+ lost het probleem op
-
De modules zijn geweesthernoemd met het voorvoegsel `testcontainers-
Fase 3: de val van hernoemde artefacten
Het hernoemen is cru. In Testcontainers 2.x werkt geen van de oude namen meer onder die naam :
| Oude naam (1.x) | Nieuwe naam (2.x) |
|---|---|
|
|
|
|
|
|
|
ongewijzigd |
Eerste poging: de BOM Testcontainers 2.0.5 toepassen en de nieuwe artefactnamen in`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.
Het BOM van testcontainers-bom is niet voldoende.Waarom? Omdat Spring Boot 3.4.5 zijn eigen BOM voor dependencymanagement blootstelt (spring-boot-dependencies) wiewinop de BOM van Testcontainers in de transitieve oplossing. Spring Boot lost op`testcontainers` à 1.20.6, en daarom alle artifacts die niet expliciet in de Spring Boot BOM staan (zoals de nieuwe`testcontainers-*) lossen naar de oude namen`1.20.6-- die niet langer bestaan in deze versie.
Tijdens het inspecteren de`dependencies --configuration testRuntimeClasspath`, we vinden :
+--- org.testcontainers:testcontainers -> 1.20.6 (*)
| \--- org.testcontainers:testcontainers:2.0.5 -> 1.20.6
Le →`geeft de vervanging aan : Testcontainers 2.0.5 is gevraagd, maar Spring Boot dwingt`1.20.6.
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle\n(consument)" as BG #E3F2FD {
rectangle "verzoek\n`testcontainers-bom:2.0.5`" as REQ
rectangle "verzoek\n`testcontainers-postgresql`" as REQ2
}
component "Maven Central" as MC #FFF3E0
component "Verklaringen" as DEC #E8F5E9 {
rectangle "`testcontainers-bom:2.0.5`\nvaste 2.0.5" as BOM2
rectangle "`spring-boot-dependencies:3.4.5`\nvaste 1.20.6" as BOM1
}
component "Resolutie Gradle" as RES #FFEBEE {
rectangle "Winnende versie\n`testcontainers:1.20.6`" as WIN #EF9A9A
}
BG --> DEC : "analyseer de BOMs"
BOM2 --> RES : "stelt voor 2.0.5"
BOM1 --> RES : "stelt voor 1.20.6"
WIN -> MC : "download\ntestcontainers:1.20.6"
note bottom of WIN
Spring Boot BOM primaire auto
het spring-boot plugin past toe
zijn afhankelijkheidsbeheer
Na de resolutie van de consument
end note
@enduml
Fase 4: de oplossing afdwingen
De strategie wordt dubbel:
-
Force docker-java naar versie 3.7.1 (getest als compatibel met Docker Engine 29)
-
Forceer Testcontainers core naar 2.0.5 door de Spring Boot BOM te omzeilen
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle\n(met resolutionStrategy)" as BG #E8F5E9 {
rectangle "BOM Testcontainers\n`2.0.5`" as BOM2
rectangle "`resolutionStrategy\n.eachDependency`" as RS #66BB6A
}
component "Conflict opgelost" as RESOLVED #E3F2FD {
rectangle "docker-java\n`3.7.1` (gedwongen)" as DJ
rectangle "testcontainers\n`2.0.5` (geforceerd)" 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(impliciet, overschreven)" as BOM1 #FFEBEE
component "Maven Centraal" as MC #FFF3E0
BG --> RESOLVED : "BOM Testcontainers 2.0.5\ngaat rechtstreeks"
RS -> DJ : "dwing 3.7.1"
RS -> TC : "kracht 2.0.5"
RESOLVED --> MC : "download de JARs\ncompatibles Docker 29"
BOM1 -[#gray,dashed]-> RESOLVED : "genegeerd op testcontainers\nen docker-java"
note bottom of RS
De `eachDependency` onderschept
de resolutie Gradle AVANT
dat het BOM Spring Boot niet wint.
Dit is een chirurgische ingreep.
end note
@enduml
Finale correctiein`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`is de enige manier om de BOM te omzeilen`spring-boot-dependencies`beheerd door de Spring Boot-plug-in. De gelijkheidstest op`details.requested.name == "testcontainers"`is deliberately restrictive: we only force the core module. The modules`testcontainers-*`het BOM van Testcontainers 2.0.5 dat we expliciet hebben verklaard.
Eindresultaat
$ ./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 slaagt er eindelijk in om hun PostgreSQL-containers te maken via Docker Engine 29.4.1. De eindzakelijke bedrijfsfout (HTTP 500 op de Cucumber-test) is een volledig toepassingsprobleem dat volledig onafhankelijk is van het buildscript.
Overzichtstabel van de correcties
| # | bestand | Probleem | Correctie |
|---|---|---|---|
1 |
|
variabele`reportsDir`niet bestaant |
|
2 |
|
`main`afkekeurde Gradle 9 |
|
3 |
|
`sourceCompatibility`top-level verboden Gradle 9 |
Blok`java { sourceCompatibility = JavaVersion.VERSION_21 }` |
4 |
|
Ongeldige assertie van de Groovy-syntaxis |
|
5 |
|
impliciete afhankelijkheid`compileKotlin`→ |
|
6 |
|
`gitPropertiesPluginVersion`onverenigbaar Java 21 |
|
7 |
Cache Gradle |
docker-java 3.5.1 getest, opgelost maar onvoldoende |
Purge + doorgang docker-java 3.7.1 |
8 |
|
Testcontainers 1.20.6 onverenigbaar Docker Engine 29 |
Forceren Testcontainers 2.0.5 + prefix`testcontainers-*`+ resolutionStrategy tegen Spring Boot BOM |
Conclusie
Als uw JHipster/Gradle-build faalt na een upgrade naar Gradle 9 + Docker Engine 29, worden de fouten in twee categorieën ingedeeld :
Fouten in build script(de eerste 6) : Gradle 9 verwijdert eigenschappen en syntaxis die al jaren werkten (sourceCompatibility=, main =, reportsDir). Dat zijn mechanische migraties zodra je de nieuwe API kent.
Docker runtime-fouten(de laatste twee) : Docker Engine 29.4.1 sluit clients API < 1.40 uit. Testcontainers 1.20.6 (opgelegd door Spring Boot 3.4.5) gebruikt docker-java 3.4.1 dat 1.32 verzendt. Alleen Testcontainers 2.0.5 + docker-java 3.7.1 lost het probleem op, met het hernoemen van de artefacten en het afdwingen van de versie via`resolutionStrategy.eachDependency`als enige manier om de impliciete BOM van Spring Boot te omzeilen
Het build script Gradle aan de wortel van het project is nu schoon.`./gradlew build`loopt op Java 21, Gradle 9.4.1, Spring Boot 3.4.5 en Docker Engine 29.4.1.