Ketika Gradle 9, Docker 29 dan Testcontainers menjadi gelisah : debugging sebuah build JHipster
Diterbitkan 25 April 2026
- Situasi: sebuah proyek JHipster, sebuah build yang tidak lagi memuat
- Kesalahan 1 : variabel fantom `reportsDir
- Error 2 :
main = \"…\"tidak ada lagi di Gradle 9 - Kesalahan 3 :
sourceCompatibilitytidak lagi merupakan properti proyek - Kesalahan 4 : pernyataan Groovy dengan tiga operand
- Error 5: dependensi implisit
compileKotlin→ `openApiGenerate - Error 6:
generateGitPropertiesgagal pada `FilterOutputStream.write() - Akhirnya: Testcontainers masuk ke panggung
- Tahap 1 : jalur docker-java
- Tahap 2 : penelusuran web dan breadcrumb GitHub
- Tahap 3: jerat artefak yang diubah namanya
- Tahap 4 : memaksakan resolusi
- Hasil akhir
- Tabel ringkasan koreksi
- Kesimpulan
Anda pernah mengalami momen ketika sesuatu yang sederhana`./gradlew build`yang Anda jalankan seratus kali sehari tiba-tiba mengalami crash pada kesalahan yang belum pernah terlihat? Kalikan dengan sebelas kesalahan berturut-turut, tambahkan dosis incompatibilitas Docker Engine 29, taburkan Gradle 9 yang menghilangkan API yang telah kita gunakan selama sepuluh tahun. Inilah catatan lengkapnya.
Tujuan: menampilkan rangkaian ketidaksesuaian
@startuml left to right direction title Migrasi Gradle 9 rectangle "1 reportsDir" as A rectangle "2\nmainClass" as B rectangle "3 sourceCompatibility" as C rectangle "4\nGroovy assert" as D A --> B B --> C C --> D @enduml
Tujuan : mengisolasi plugin yang rusak.
@startuml left to right direction title Plugins yang perlu diperbaiki skinparam shadowing false skinparam roundcorner 18 rectangle "1\nopenApiGenerate" as A #FFF3E0 rectangle "2 gitProperties 2.5.7" as B #FFF3E0 rectangle "3 Versi yang diperbaiki" as C #E3F2FD rectangle "4 Mengasambel OK" as D #E8F5E9 A --> B B --> C C --> D @enduml
Tujuan : masalah runtime.
@startuml left to right direction title Docker 29 + Testcontainers skinparam shadowing false skinparam roundcorner 18 skinparam defaultFontSize 14 rectangle "1\nSusun OK" as A #E8F5E9 rectangle "2 Ujian Cucumber GAGAL" as B #FFEBEE rectangle "3 Testcontainers 1.32 ditolak" as C #FFF3E0 rectangle "4 Upgrade versi" as D #E3F2FD rectangle "5\nBuild akhir 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 Peta jalan stabilisasi — Gradle 9 ke build final left to right direction rectangle "⚙️ Migrasi Gradle 9" as A #E3F2FD rectangle "🧩 Plugin JHipster" as B #FFF3E0 rectangle "🐳 Docker 29 Testcontainers" 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 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
Situasi: sebuah proyek JHipster, sebuah build yang tidak lagi memuat
Proyek`edster`adalah sebuah aplikasi JHipster yang dihasilkan pada tahun 2024. Skrip build akar`build.gradle`est tetap stabil selama beberapa bulan. Lalu kami memutuskan untuk meningkatkan versinya:
Komponen |
Versi target |
Gradle |
9.4.1 |
Java |
21.0.11-tem (Eclipse Temurin) |
JHipster |
8.x (kerangka kerja`tech.jhipster:jhipster-framework:8.11.0`) |
Spring Boot |
3.4.5 |
Kotlin |
2.3.0 |
Docker Engine |
29.4.1 (API 1.54) |
Perubahan versi Java melalui SDKMAN :
sdk use java 21.0.11-tem
Peluncuran pertama:
$ ./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'
Kami bahkan tidak bisa meluncur dari pemuatan build script. Masalahnya ada di`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.
Kesalahan 1 : variabel fantom `reportsDir
skrip`jhipster.cucumber-conventions.gradle`mengandung :
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"
// ...
}
Variabel yang didefinisikan adalah`cucumberReportsDir`. Yang digunakan adalah`reportsDir`-- yang tidak ditentukan di mana-mana.
Perbaikan : outputs.dir(cucumberReportsDir)
Error 2 : main = \"…\" tidak ada lagi di Gradle 9
Ulang segera:
Could not set unknown property 'main' for task ':consoleLauncherTest' of type org.gradle.api.tasks.JavaExec.
Gradle 9 menghapus properti`main`untuk keuntungan`mainClass`pada tugas-tugas`JavaExec`.
Perbaikan:`mainClass = "org.junit.platform.console.ConsoleLauncher"`
Kesalahan 3 : sourceCompatibility tidak lagi merupakan properti proyek
Kesalahan baru :
Could not set unknown property 'sourceCompatibility' for root project 'edster' of type org.gradle.api.Project.
Skrip root memiliki:
sourceCompatibility=17
targetCompatibility=17
Gradle 9 menghapus properti-properti ini pada level root. Mereka harus berada di dalam sebuah blok`java`.
Koreksi:
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
Kesalahan 4 : pernyataan Groovy dengan tiga operand
Skrip lama mengandung:
assert System.properties["java.specification.version"] == "17" || "21" || "24"
Di Groovy,"21" et "24"`adalah string truthy. Ekspresi menyelesaikan ke(… == "17") || true || true`, yang selalu benar — tetapi sintaksnya tidak valid untuk pernyataan ketat yang diinginkan.
Koreksi:
assert System.properties["java.specification.version"] in ["17", "21", "23", "24"]
Error 5: dependensi implisit compileKotlin → `openApiGenerate
Build sedang berjalan. Kompilasi Kotlin berhasil. Lalu :
Task ':compileKotlin' uses this output of task ':openApiGenerate'
without declaring an explicit or implicit dependency.
Gradle 9 menolak dependensi implisit antara tugas. Karena`compileKotlin`membaca sumber yang dihasilkan oleh`openApiGenerate`, harus mendeklarasikan tautan.
perbaikandalam`build.gradle`:
afterEvaluate {
tasks.named("compileKotlin").configure {
dependsOn(tasks.named("openApiGenerate"))
}
}
Le `afterEvaluate`diperlukan karena`openApiGenerate`adalah sebuah ekstensi (plugin OpenAPI Generator) dan bukan sebuah tugas yang dapat diakses langsung selama fase konfigurasi.
Error 6: generateGitProperties gagal pada `FilterOutputStream.write()
Compilasi Java berhasil. Sumber daya diproses. Kemudian :
> Task :generateGitProperties FAILED
No signature of method: java.io.FilterOutputStream.write() is applicable for argument types: (Integer) values: [103]
plugin`gradle-git-properties`Versi 2.5.0 tidak kompatibel dengan Java 21 / Gradle 9. Bug internal mencoba memanggil`write(int)`via Groovy pada aliran yang ditutup
Perbaikandi`gradle.properties` :
gitPropertiesPluginVersion=2.5.7
Akhirnya: Testcontainers masuk ke panggung
Sampai sekarang, ini hanya 'build script debugging' murni. Setiap kesalahan adalah ketidakcocokan Gradle 9 atau Java 21 dalam skrip build. Setelah enam perbaikan di atas, le`./gradlew assemble`berhasil lolos.
Tapi`./gradlew build`menjalankan juga tes, dan tes Cucumber menggunakan Testcontainers untuk menjalankan PostgreSQL sementara.
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 tidak mampu berkomunikasi dengan Docker. Dua strategi berbeda (variabel lingkungan + soket Unix) gagal pada kesalahan yang sama.
@startuml skinparam backgroundColor #FEFEFE skinparam handwritten false actor "Testcontainers 1.20.6" as TC participant "docker-java 3.4.1" as DJ participant "Docker Engine 29.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
Tahap 1 : jalur docker-java
Refleks pertama : klien Docker Java yang digunakan oleh Testcontainers mengirim versi API`1.32`dalam handshake HTTP-nya, tetapi Docker Engine 29.4.1 memerlukan minimum`1.40`. Masalahnya ada di sisi klien, bukan pada daemon.
Pemeriksaan versi docker-java yang diselesaikan oleh 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, yang berasal dari Testcontainers 1.20.6. Versi publik terbaru dari docker-java adalah 3.5.1. Mari kita coba memaksa peningkatan versi melalui`resolutionStrategy`dalam 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")
}
}
}
}
Ulangi build. Kesalahan yang sama :`client version 1.32 is too old`.
Verifikasi cache Gradle: JARs telah di-download ulang dengan baik, tetapi kesalahan tetap ada. docker-java 3.5.1 masih mengirim`1.32`. Versi ini TIDAK cukup untuk Docker Engine 29.4.1.
Tindakan penyucian: membersihkan sepenuhnya cache Gradle, hanya untuk memastikan :
rm -rf ~/.gradle/caches
rm -rf /path/to/project/.gradle
Restart lengkap setelah purge. Kesalahan yang sama.
Tahap 2 : penelusuran web dan breadcrumb GitHub
docker-java 3.5.1 tidak mencukupi. Hanya perlu mencari apakah seseorang telah mengalami masalah ini.
Pencarian terarah pada isu testcontainers-java dan 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
Hasil segera pertama :
-
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(No content to translate)
[Bug]: Incompatibility in Ubuntu Github runners with Docker Engine 29.1.*
breadcrumb jelas: Docker Engine 29.x telah meningkatkan versi minimum API-nya menjadi`1.24`(bug lama) ke`1.40`. Testcontainers 1.20.x menggunakan docker-java 3.4.x yang negoiasi versi API dengan mengirim`1.32`. Daemon menolak dengan sopan tapi tegas.
Dalam isu #11491, seorang pemelihara menjawab:
_ Hai, saya memiliki proyek yang berjalan dengan versi 2.0.3 di GH runner dengan mesin 29.1 dan ini berfungsi. Bisakah kalian semua mohon memeriksa bahwa tidak ada konflik versi? Mohon, ingat semua modul telah diawali dengan`testcontainers-. Jadi, mulai dengan versi 2.x, kita beralih dari`postgresql to testcontainers-postgresql _
Jawaban ini berisidua informasi kritis:
-
Testcontainers 2.0.3+ menyelesaikan masalah
-
Modul telahdiberi nama ulang dengan awalan `testcontainers-
Tahap 3: jerat artefak yang diubah namanya
Penggantian nama ini brutal. Di Testcontainers 2.x, tidak ada nama lama yang lagi berfungsi dengan nama itu:
Nama lama (1.x) |
Nama baru (2.x) |
|
|
|
|
|
|
|
tidak berubah |
Percobaan pertama: menerapkan BOM Testcontainers 2.0.5 dan nama-nama artifact baru di`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 tidak cukup.Kenapa ? Karena Spring Boot 3.4.5 mengekspos BOM manajemen dependensi-nya sendiri (spring-boot-dependencies) yangmenangpada BOM Testcontainers dalam resolusi transitif. Spring Boot memperbaiki`testcontainers` à 1.20.6, dan karena itu semua artefak yang tidak secara eksplisit berada dalam BOM Spring Boot (seperti yang baru`testcontainers-*) mengarah ke nama-nama lama`1.20.6-- yang tidak lagi ada dalam versi ini.
Saat memeriksa`dependencies --configuration testRuntimeClasspath`, ada :
+--- org.testcontainers:testcontainers -> 1.20.6 (*)
| \--- org.testcontainers:testcontainers:2.0.5 -> 1.20.6
Le →`menampilkan penggantian : Testcontainers 2.0.5 diminta, tapi Spring Boot memaksa`1.20.6.
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle
(konsumen)" as BG #E3F2FD {
rectangle "permintaan\n`testcontainers-bom:2.0.5" as REQ
rectangle "permintaan
`testcontainers-postgresql" as REQ2
}
component "Maven Central" as MC #FFF3E0
component "Pernyataan" as DEC #E8F5E9 {
rectangle "testcontainers-bom:2.0.5`\ndiperbaiki 2.0.5" as BOM2
rectangle "spring-boot-dependencies:3.4.5`
tetap 1.20.6" as BOM1
}
component "Resolusi Gradle" as RES #FFEBEE {
rectangle "Versi pemenang\n`testcontainers:1.20.6" as WIN #EF9A9A
}
BG --> DEC : "menganalisis BOMs"
BOM2 --> RES : "menyatakan 2.0.5"
BOM1 --> RES : "menawarkan 1.20.6"
WIN -> MC : "download\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
Tahap 4 : memaksakan resolusi
Strategi menjadi ganda:
-
Memaksa docker-java pada versi 3.7.1 (diuji sebagai kompatibel dengan Docker Engine 29)
-
Memaksa Testcontainers core pada versi 2.0.5 dengan melewati BOM Spring Boot
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle
(dengan resolutionStrategy)" as BG #E8F5E9 {
rectangle "BOM Testcontainers\n`2.0.5" as BOM2
rectangle "resolutionStrategy\n.eachDependency" as RS #66BB6A
}
component "Konflik terpecahkan" as RESOLVED #E3F2FD {
rectangle "docker-java
`3.7.1` (dipaksa)" as DJ
rectangle "testcontainers\n`2.0.5` (dipaksa)" as TC #81C784
rectangle "testcontainers-postgresql
`2.0.5" as TPG
rectangle "testcontainers-jdbc\n`2.0.5" as TJ
rectangle "testcontainers-junit-jupiter
`2.0.5" as TJJ
}
component "BOM Spring Boot
(implisit, dioverride)" as BOM1 #FFEBEE
component "Maven Central" as MC #FFF3E0
BG --> RESOLVED : "BOM Testcontainers 2.0.5
melewati langsung"
RS -> DJ : "kekuatan 3.7.1"
RS -> TC : "gaya 2.0.5"
RESOLVED --> MC : "unduh JARs\nkompatibel Docker 29"
BOM1 -[#gray,dashed]-> RESOLVED : "diabaikan pada 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
Koreksi finaldalam`build.gradle`(Empty output)
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`adalah satu-satunya cara untuk mengabaikan BOM`spring-boot-dependencies`Dikelola oleh plugin Spring Boot. Uji kesamaan pada`details.requested.name == "testcontainers"`adalah sengaja restriktif: kita hanya memaksa modul core. Modul-modul`testcontainers-*`mereka mengikuti BOM Testcontainers 2.0.5 yang telah kami deklarasikan secara eksplisit
Hasil akhir
$ ./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 akhirnya berhasil membuat containernya PostgreSQL melalui Docker Engine 29.4.1. Kesalahan bisnis akhir (HTTP 500 pada ujian Cucumber) adalah masalah aplikasi yang sepenuhnya independen dari skrip build.
Tabel ringkasan koreksi
| # | File | Masalah | korreksi |
|---|---|---|---|
1 |
|
Variabel`reportsDir`tidak ada |
|
2 |
|
`main`usang Gradle 9 |
|
3 |
|
`sourceCompatibility`top-level dilarang Gradle 9 |
blok`java { sourceCompatibility = JavaVersion.VERSION_21 }` |
4 |
|
Pernyataan sintaksis Groovy yang tidak valid |
|
5 |
|
Ketergantungan implisit`compileKotlin`→ |
|
6 |
|
`gitPropertiesPluginVersion`tidak kompatibel Java 21 |
|
7 |
Cache Gradle |
docker-java 3.5.1 diuji, diselesaikan, namun tidak cukup |
Pembersihan + perjalanan docker-java 3.7.1 |
8 |
|
Testcontainers 1.20.6 tidak kompatibel dengan Docker Engine 29 |
Memaksa Testcontainers 2.0.5 + prefix`testcontainers-*`+ strategi resolusi vs Spring Boot BOM |
Kesimpulan
Jika build JHipster/Gradle Anda gagal setelah upgrade ke Gradle 9 + Docker Engine 29, kesalahannya terbagi menjadi dua kategori :
Kesalahan skrip build(6 pertama) : Gradle 9 menghapus properti dan sintaksis yang berfungsi selama bertahun-tahun (sourceCompatibility=, main =, reportsDir). Mereka adalah migrasi mekanis setelah Anda mengenal API baru.
Kesalahan runtime Docker(kedua terakhir): Docker Engine 29.4.1 mengabaikan klien API < 1.40. Testcontainers 1.20.6 (dipaksa oleh Spring Boot 3.4.5) menggunakan docker-java 3.4.1 yang mengirimkan 1.32. Hanya Testcontainers 2.0.5 + docker-java 3.7.1 yang menyelesaikan masalah, dengan penamaan ulang artefak dan penaklukan versi via`resolutionStrategy.eachDependency`sebagai satu-satunya cara untuk mengatasi BOM implisit Spring Boot.
Skrip build Gradle di akar proyek sekarang bersih.`./gradlew build`melewati atas Java 21, Gradle 9.4.1, Spring Boot 3.4.5 dan Docker Engine 29.4.1