When Gradle 9, Docker 29, and Testcontainers clash: debugging a JHipster build
Published on 25 April 2026
- The scene: a JHipster project, a build that no longer loads
- Error 1: the phantom variable
reportsDir - Error 2:
main = "…"no longer exists under Gradle 9 - Error 3:
sourceCompatibilityis no longer a project property - Error 4: the three-operand Groovy assertion
- Error 5: the implicit dependency
compileKotlin→openApiGenerate - Error 6:
generateGitPropertiesbreaks onFilterOutputStream.write() - Finally: Testcontainers enters the scene
- Phase 1: the docker-java lead
- Phase 2: web search and GitHub breadcrumbs
- Phase 3: the trap of renamed artifacts
- Phase 4: forcing the resolution
- Final result
- Summary table of corrections
- Conclusion
Have you ever had that moment where a simple`./gradlew build`that you run a hundred times a day suddenly starts crashing with an error you’ve never seen? Multiply that by eleven successive errors, add a healthy dose of Docker Engine 29 incompatibility, sprinkle in some Gradle 9 making APIs you’ve used for ten years disappear. Here is the complete log.
Goal: show the series of incompatibilities.
Goal: isolate the broken plugins.
Goal: runtime problem.
The scene: a JHipster project, a build that no longer loads
The project`edster`is a JHipster application generated in 2024. The root build script`build.gradle`remained stable for months. Then we decided to upgrade:
Component |
Target Version |
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) |
The Java version change is handled via SDKMAN:
sdk use java 21.0.11-tem
First run:
$ ./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 don’t even get past the build script loading. The problem is in`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.
Error 1: the phantom variable reportsDir
The script`jhipster.cucumber-conventions.gradle`contains:
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"
// ...
}
The defined variable is`cucumberReportsDir`. The one used is`reportsDir`-- which is not defined anywhere.
Correction:`outputs.dir(cucumberReportsDir)`
Error 2: main = "…" no longer exists under Gradle 9
Immediate restart:
Could not set unknown property 'main' for task ':consoleLauncherTest' of type org.gradle.api.tasks.JavaExec.
Gradle 9 removes the property`main`in favor of`mainClass`on`JavaExec`tasks.
Correction:`mainClass = "org.junit.platform.console.ConsoleLauncher"`
Error 3: sourceCompatibility is no longer a project property
New error:
Could not set unknown property 'sourceCompatibility' for root project 'edster' of type org.gradle.api.Project.
The root script had:
sourceCompatibility=17
targetCompatibility=17
Gradle 9 removes these properties at the root level. They must reside in a`java`block.
Correction:
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
Error 4: the three-operand Groovy assertion
The old script contained:
assert System.properties["java.specification.version"] == "17" || "21" || "24"
In Groovy,"21" et "24"`are truthy strings. The expression resolves to(… == "17") || true || true`, which is always true — but the syntax is invalid for the desired strict assertion.
Correction:
assert System.properties["java.specification.version"] in ["17", "21", "23", "24"]
Error 5: the implicit dependency compileKotlin → openApiGenerate
The build progresses. Kotlin compilation successful. Then:
Task ':compileKotlin' uses this output of task ':openApiGenerate'
without declaring an explicit or implicit dependency.
Gradle 9 refuses implicit dependencies between tasks. Since`compileKotlin`reads the sources generated by`openApiGenerate`, the link must be declared.
Correctionin`build.gradle`:
afterEvaluate {
tasks.named("compileKotlin").configure {
dependsOn(tasks.named("openApiGenerate"))
}
}
Le `afterEvaluate`is necessary because`openApiGenerate`is an extension (openAPI Generator plugin) and not a task directly accessible during the configuration phase.
Error 6: generateGitProperties breaks on FilterOutputStream.write()
Java compilation passes. Resources are processed. Then:
> Task :generateGitProperties FAILED
No signature of method: java.io.FilterOutputStream.write() is applicable for argument types: (Integer) values: [103]
The`gradle-git-properties`plugin version 2.5.0 is incompatible with Java 21 / Gradle 9. An internal bug attempts to call`write(int)`via Groovy on a stream that has been closed.
Correctionin`gradle.properties`:
gitPropertiesPluginVersion=2.5.7
Finally: Testcontainers enters the scene
Until now, it was pure "build script debugging." Each error was a Gradle 9 or Java 21 incompatibility in the build scripts. After the six corrections above, the`./gradlew assemble`passes successfully.
But`./gradlew build`also executes tests, and Cucumber tests use Testcontainers to launch an ephemeral 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 is unable to communicate with Docker. Two different strategies (environment variables + Unix socket) fail with the same error.
Phase 1: the docker-java lead
First reflex: the Java Docker client used by Testcontainers sends API version`1.32`in its HTTP handshake, but Docker Engine 29.4.1 requires a minimum of`1.40`. The problem is at the client level, not the daemon.
Verification of the docker-java version resolved by 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, dating from Testcontainers 1.20.6. The latest public version of docker-java is 3.5.1. Let’s try to force the version upgrade via`resolutionStrategy`in the`configurations` de `build.gradle`block:
configurations {
all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == "com.github.docker-java"
&& details.requested.name.startsWith("docker-java")) {
details.useVersion("3.5.1")
}
}
}
}
Rerunning the build. Same error:`client version 1.32 is too old`.
Verifying the Gradle cache: the JARs were indeed re-downloaded, but the error persists. docker-java 3.5.1 still sends`1.32`. This version is therefore NOT sufficient for Docker Engine 29.4.1.
Purification action: completely clear the Gradle cache, just to be sure:
rm -rf ~/.gradle/caches
rm -rf /path/to/project/.gradle
Complete restart after purge. Same error.
Phase 2: web search and GitHub breadcrumbs
docker-java 3.5.1 is insufficient. Nothing left but to search if someone has encountered this exact problem.
Targeted search on testcontainers-java and docker-java issues:
site:github.com/testcontainers "client version 1.32 is too old"
site:github.com/docker-java "client version 1.32" Docker Engine 29
Immediate first results:
-
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.*
The breadcrumbs are clear: Docker Engine 29.x raised its minimum API version from`1.24`(former default) to`1.40`. Testcontainers 1.20.x uses docker-java 3.4.x which negotiates the API version by sending`1.32`. The daemon politely but firmly refuses.
In issue #11491, a maintainer responds:
_ Hi, I have a project running with version 2.0.3 on a GH runner with engine 29.1 and it works. Can you all please check there is no version conflict? Please, remember all modules were prefixed with`testcontainers-. So, starting with version 2.x we went from`postgresql to testcontainers-postgresql _
This response containstwo critical pieces of information:
-
Testcontainers 2.0.3+ resolves the problem
-
The modules have beenrenamed with the
testcontainers-prefix
Phase 3: the trap of renamed artifacts
The renaming is brutal. In Testcontainers 2.x, none of the old names work under that name anymore:
Old name (1.x) |
New name (2.x) |
|
|
|
|
|
|
|
unchanged |
First attempt: apply the Testcontainers 2.0.5 BOM and the new artifact names 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.
The testcontainers-bom BOM is not enough.Why? Because Spring Boot 3.4.5 exposes its own dependency management BOM (spring-boot-dependencies) whichwinsover the Testcontainers BOM in transitive resolution. Spring Boot fixes`testcontainers` à 1.20.6, and thus all artifacts not explicitly in the Spring Boot BOM (like the new`testcontainers-*) resolve to the old names`1.20.6-- which no longer exist in this version.
Inspecting the`dependencies --configuration testRuntimeClasspath`, we find:
+--- org.testcontainers:testcontainers -> 1.20.6 (*)
| \--- org.testcontainers:testcontainers:2.0.5 -> 1.20.6
Le →`indicates the substitution: Testcontainers 2.0.5 is requested, but Spring Boot forces`1.20.6.
Phase 4: forcing the resolution
The strategy becomes twofold:
-
Force docker-java to its 3.7.1 version (tested as compatible with Docker Engine 29)
-
Force Testcontainers core to 2.0.5 by bypassing the Spring Boot BOM
Final correctionin`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 the only way to override the`spring-boot-dependencies`BOM managed by the Spring Boot plugin. The equality test on`details.requested.name == "testcontainers"`is intentionally restrictive: we only force the core module. The`testcontainers-*`modules follow the Testcontainers 2.0.5 BOM that we explicitly declared.
Final result
$ ./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 finally manage to create their PostgreSQL containers via Docker Engine 29.4.1. The final business error (HTTP 500 on the Cucumber test) is an application problem totally independent of the build script.
Summary table of corrections
| # | File | Problem | Correction |
|---|---|---|---|
1 |
|
Non-existent`reportsDir`variable |
|
2 |
|
`main`Gradle 9 deprecated |
|
3 |
|
`sourceCompatibility`top-level forbidden Gradle 9 |
Bloc |
4 |
|
Syntactically invalid Groovy assertion |
|
5 |
|
Implicit dependency`compileKotlin`→ |
|
6 |
|
`gitPropertiesPluginVersion`Java 21 incompatible |
|
7 |
Gradle cache |
docker-java 3.5.1 tested, resolved but insufficient |
Purge + move to docker-java 3.7.1 |
8 |
|
Testcontainers 1.20.6 incompatible with Docker Engine 29 |
Force Testcontainers 2.0.5 + prefix`testcontainers-*`+ resolutionStrategy vs Spring Boot BOM |
Conclusion
If your JHipster/Gradle build crashes after upgrading to Gradle 9 + Docker Engine 29, the errors fall into two categories:
Build script errors(the first 6): Gradle 9 removes properties and syntaxes that had worked for years (sourceCompatibility=, main =, reportsDir). These are mechanical migrations once you know the new API.
Docker runtime errors(the last 2): Docker Engine 29.4.1 rejects API clients < 1.40. Testcontainers 1.20.6 (imposed by Spring Boot 3.4.5) uses docker-java 3.4.1 which sends 1.32. Only Testcontainers 2.0.5 + docker-java 3.7.1 resolves the problem, with artifact renaming and version forcing via`resolutionStrategy.eachDependency`as the only way to bypass the implicit Spring Boot BOM.
The Gradle build script at the project root is now clean.`./gradlew build`passes on Java 21, Gradle 9.4.1, Spring Boot 3.4.5, and Docker Engine 29.4.1.