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가 무대에 등장합니다.
- Phase 1 : docker-java 트랙
- 2단계 : 웹 검색 및 GitHub 브레드크럼
- 3단계 : 이름이 바뀐 아티팩트의 함정
- 4단계 : 해결 강제
- 최종 결과
- 수정 사항 요약표
- 결론
당신은 이미 그 순간을 경험했습니다. 단순한`./gradlew build`당신이 하루에 100번 실행하는 것이 갑자기 한 번도 본 적 없는 오류로 인해 멈추기 시작한다면? 이를 연속 11번의 오류로 곱하고, Docker Engine 29 호환성 문제를 충분히 추가한 뒤, 10년 동안 사용해 온 API를 삭제하는 Gradle 9을 뿌리세요. 여기서 전체 로그를 확인할 수 있습니다.
목적 : 불호환 시리즈를 보여주다
@startuml left to right direction title Gradle 9 마이그레이션 rectangle "1 reportsDir" as A rectangle "2 mainClass" as B rectangle "3\nsourceCompatibility" as C rectangle "4 Groovy assert" as D A --> B B --> C C --> D @enduml
목표 : 손상된 플러그인 격리
@startuml left to right direction title 수정해야 할 플러그인 skinparam shadowing false skinparam roundcorner 18 rectangle "1 openApiGenerate" as A #FFF3E0 rectangle "2 gitProperties 2.5.7" as B #FFF3E0 rectangle "3 버전 수정된" as C #E3F2FD rectangle "4 조립 OK" 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 조립 OK" as A #E8F5E9 rectangle "2\n테스트 Cucumber 실패" as B #FFEBEE rectangle "3 Testcontainers 1.32 거부" as C #FFF3E0 rectangle "4 업그레이드 버전" 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 |
자바 |
21.0.11-tem (Eclipse Temurin) |
JHipster |
8.x (프레임워크`tech.jhipster:jhipster-framework:8.11.0`) |
스프링 부트 |
3.4.5 |
코틀린 |
2.3.0 |
도커 엔진 |
29.4.1 (API 1.54) |
자바 버전 변경은 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)`그루비를 통해 스트림이 닫힌 것.
수정안에`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 "도커 엔진 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
Phase 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 :
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에서, 유지관리자가 답변합니다 :
_ 안녕하세요, 저는 GH 러너에서 엔진 29.1을 사용하는 버전 2.0.3으로 실행되는 프로젝트가 있는데, 잘 작동합니다. 모두 버전 충돌이 없는지 확인해 주실 수 있나요? 모든 모듈이 무언가로 prefix되었음을 기억해 주세요`testcontainers-. 그래서 버전 2.x부터 우리는 …부터 시작했습니다`postgresql to testcontainers-postgresql _
이 답변에는두 개의 중요한 정보:
-
Testcontainers 2.0.3+은 문제를 해결합니다
-
모듈들이testcontainers-` 접두사로 이름이 바뀐
3단계 : 이름이 바뀐 아티팩트의 함정
이름 변경은 가혹합니다. Testcontainers 2.x에서는 더 이상 과거 이름 중 어느 것도 해당 이름으로 작동하지 않습니다:
이전 이름 (1.x) |
새 이름 (2.x) |
|
|
|
|
|
|
|
변경되지 않은 |
첫 번째 시도: Testcontainers BOM 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.
testcontainers-bom의 BOM은 충분하지 않다.왜? Spring Boot 3.4.5가 자체적인 의존성 관리 BOM을 노출하기 때문에 (spring-boot-dependencies) 누구이긴다Testcontainers BOM의 전달적 해결에서 Spring Boot이 고정합니다`testcontainers` à 1.20.6, 따라서 Spring Boot BOM에 명시적으로 들어 있지 않은 모든 아티팩트(예: 새로운`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
(소비자)" as BG #E3F2FD {
rectangle "요청
`testcontainers-bom:2.0.5" as REQ
rectangle "요청
`testcontainers-postgresql" as REQ2
}
component "Maven 중앙" as MC #FFF3E0
component "선언" as DEC #E8F5E9 {
rectangle "testcontainers-bom:2.0.5`
수정 2.0.5" as BOM2
rectangle "spring-boot-dependencies:3.4.5`\n고정 1.20.6" as BOM1
}
component "Gradle 해결" as RES #FFEBEE {
rectangle "우승 버전
`testcontainers:1.20.6" as WIN #EF9A9A
}
BG --> DEC : "BOM을 분석하다"
BOM2 --> RES : "제안 2.0.5"
BOM1 --> RES : "제안하다 1.20.6"
WIN -> MC : "다운로드
testcontainers: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와 호환됨으로 테스트됨)
-
Spring Boot BOM을 우회하여 Testcontainers core를 2.0.5로 강제
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle
(resolutionStrategy와 함께)" as BG #E8F5E9 {
rectangle "BOM Testcontainers
`2.0.5" as BOM2
rectangle "resolutionStrategy\n.eachDependency" as RS #66BB6A
}
component "해결된 갈등" as RESOLVED #E3F2FD {
rectangle "docker-java
`3.7.1` (강제)" as DJ
rectangle "testcontainers
`2.0.5` (강제)" as TC #81C784
rectangle "testcontainers-postgresql
`2.0.5" as TPG
rectangle "testcontainers-jdbc
`2.0.5" as TJ
rectangle "testcontainers-junit-jupiter
`2.0.5" as TJJ
}
component "BOM Spring Boot
(암시적, 재정의된)" 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를 다운로드하세요\nDocker 29와 호환되는"
BOM1 -[#gray,dashed]-> RESOLVED : "testcontainers에서 무시됨\ndocker-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-*`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`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 런타임 오류(마지막 두) : 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만이 문제를 해결하며, 아티팩트 이름 변경 및 버전 강제를 통해`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