leestijd : 15 minutes

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.


Diagram 1 — Gradle 9 migration (foutenketen)

Doel: de reeks onverenigbaarheden tonen.

👉 Duidelijk, gericht op Gradle.
@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

Diagram 2 — Oncompatibele JHipster-plugins

Doel: de defecte plugins isoleren.

👉 Duidelijk beeld van de afhankelijkheden.
@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

Diagram 3 — Docker 29 / Testcontainers

Doel: runtime-probleem

👉 Duidelijke geschiedenis van de runtime.
@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

een andere manier om het zich voor te stellen
@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 :

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 :

  1. Testcontainers 2.0.3+ lost het probleem op

  2. 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)

org.testcontainers:postgresql

org.testcontainers:testcontainers-postgresql

org.testcontainers:jdbc

org.testcontainers:testcontainers-jdbc

org.testcontainers:junit-jupiter

org.testcontainers:testcontainers-junit-jupiter

org.testcontainers:testcontainers

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:

  1. Force docker-java naar versie 3.7.1 (getest als compatibel met Docker Engine 29)

  2. 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

buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle

variabele`reportsDir`niet bestaant

outputs.dir(cucumberReportsDir)

2

buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle

`main`afkekeurde Gradle 9

mainClass

3

build.gradle

`sourceCompatibility`top-level verboden Gradle 9

Blok`java { sourceCompatibility = JavaVersion.VERSION_21 }`

4

build.gradle

Ongeldige assertie van de Groovy-syntaxis

assert …​ in ["17", "21", "23", "24"]

5

build.gradle

impliciete afhankelijkheid`compileKotlin`→openApiGenerate

afterEvaluate { tasks.named("compileKotlin").dependsOn("openApiGenerate") }

6

gradle.properties

`gitPropertiesPluginVersion`onverenigbaar Java 21

2.5.7

7

Cache Gradle

docker-java 3.5.1 getest, opgelost maar onvoldoende

Purge + doorgang docker-java 3.7.1

8

build.gradle

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.

Gerelateerde artikelen