Quando Gradle 9, Docker 29 e Testcontainers estão fervendo : debugando um build JHipster
Publicado em 25 April 2026
- A cena: um projeto JHipster, um build que não carrega mais
- Erro 1: a variável fantasma `reportsDir
- Erro 2 :
main = "…"não existe mais em Gradle 9 - Erro 3 :
sourceCompatibilitynão é mais uma propriedade do projeto - Erro 4 : a asserção Groovy com três operandos
- Erro 5 : a dependência implícita
compileKotlin→ `openApiGenerate - Erro 6 :
generateGitPropertiesfalha em `FilterOutputStream.write() - Finalmente : Testcontainers entra em cena
- Fase 1 : a pista docker-java
- Fase 2 : pesquisa web e trilha de navegação GitHub
- Fase 3 : a armadilha dos artefatos renomeados
- Fase 4 : forçar a resolução
- Resultado final
- Tabela resumo das correções
- Conclusão
Já conheceu esse momento onde um simples`./gradlew build`que você executa cem vezes por dia passa a travar repentinamente com um erro nunca visto? Multiplique isso por onze erros sucessivos, adicione uma boa dose de incompatibilidade do Docker Engine 29, polvilhe com Gradle 9 que faz desaparecer APIs que utilizávamos há dez anos. Eis o registro de bordo completo.
Objetivo: mostrar a série de incompatibilidades.
@startuml left to right direction title Migration Gradle 9 rectangle "1\ndiretório de relatórios" as A rectangle "2\nclasse principal" as B rectangle "3\ncompatibilidade do código-fonte" as C rectangle "4\nassert Groovy" as D A --> B B --> C C --> D @enduml
Objetivo: isolar os plugins quebrados.
@startuml left to right direction title Plugins a corrigir skinparam shadowing false skinparam roundcorner 18 rectangle "1\nopenApiGenerate" as A #FFF3E0 rectangle "2\ngitProperties\n2.5.7" as B #FFF3E0 rectangle "3\nVersions\ncorrigidas" as C #E3F2FD rectangle "4\nAssemble OK" as D #E8F5E9 A --> B B --> C C --> D @enduml
Objetivo: problema em tempo de execução.
@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\nTests Cucumber FAIL" as B #FFEBEE rectangle "3\nTestcontainers 1.32\nrejeitado" as C #FFF3E0 rectangle "4\nUpgrade\nversions" as D #E3F2FD rectangle "5\nBuild final 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 Roadmap de Estabilização — Gradle 9 para o Build Final left to right direction rectangle "⚙️ Migração Gradle 9" as A #E3F2FD rectangle "🧩 Plugins JHipster" as B #FFF3E0 rectangle "🐳 Docker 29\nTestcontainers" as C #FCE4EC rectangle "✅ Construção estável" as D #E8F5E9 note bottom of A reportsDir mainClass sourceCompatibility Groovy assert end note note bottom of B openApiGenerate gitProperties versões alinhadas end note note bottom of C testes Cucumber KO imagem Docker rejeitada atualizar libs adoção comum em logs técnicos de manter "libs" como abreviatura. Não há acentos graves no input para preservar, portanto a saída está em conformidade com a regra. Apenas o texto traduzido é fornecido, conforme solicitado. end note A -[#42A5F5]-> B : correções build.gradle B -[#FB8C00]-> C : Montar OK C -[#E53935]-> D : testes reparados @enduml
A cena: um projeto JHipster, um build que não carrega mais
O projeto`edster`é uma aplicação JHipster gerada em 2024. O script de construção raiz`build.gradle`ficou estável durante meses. Depois disso, decide atualizar a versão:
| Componente | (No input text was provided for translation.) |
|---|---|
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) |
A mudança da versão do Java passa via SDKMAN :
sdk use java 21.0.11-tem
Primeiro lançamento:
$ ./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'
Nem mesmo conseguimos sair do carregamento do script de build. O problema está em`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.
Erro 1: a variável fantasma `reportsDir
O script`jhipster.cucumber-conventions.gradle`contém :
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"
// ...
}
A variável definida é`cucumberReportsDir`. A utilizada é`reportsDir`-- que não está definida em nenhum lugar.
Correção:`outputs.dir(cucumberReportsDir)`
Erro 2 : main = "…" não existe mais em Gradle 9
Relançamento imediato :
Could not set unknown property 'main' for task ':consoleLauncherTest' of type org.gradle.api.tasks.JavaExec.
Gradle 9 remove uma propriedade`main`em benefício de`mainClass`sobre as tarefas`JavaExec`.
Correção : mainClass = "org.junit.platform.console.ConsoleLauncher"
Erro 3 : sourceCompatibility não é mais uma propriedade do projeto
Novo erro:
Could not set unknown property 'sourceCompatibility' for root project 'edster' of type org.gradle.api.Project.
O script raiz tinha:
sourceCompatibility=17
targetCompatibility=17
Gradle 9 remove estas propriedades no nível raiz. Elas devem estar dentro de um bloco`java`(No output.)
Correção:
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
Erro 4 : a asserção Groovy com três operandos
O antigo script continha:
assert System.properties["java.specification.version"] == "17" || "21" || "24"
Em Groovy,"21" et "24"`são strings truthy. A expressão resolve em(… == "17") || true || true`, que é sempre verdadeira — mas a sintaxe está inválida para a asserção estrita desejada.
Correção :
assert System.properties["java.specification.version"] in ["17", "21", "23", "24"]
Erro 5 : a dependência implícita compileKotlin → `openApiGenerate
O build está progredindo. Compilação Kotlin bem-sucedida. Então:
Task ':compileKotlin' uses this output of task ':openApiGenerate'
without declaring an explicit or implicit dependency.
Gradle 9 recusa as dependências implícitas entre tarefas. Desde que`compileKotlin`lê as fontes geradas por`openApiGenerate`, é necessário declarar o vínculo.
Correçãodentro de`build.gradle`(Empty)
afterEvaluate {
tasks.named("compileKotlin").configure {
dependsOn(tasks.named("openApiGenerate"))
}
}
Le `afterEvaluate`é necessário pois`openApiGenerate`é uma extensão (plugin OpenAPI Generator) e não uma tarefa diretamente acessível durante a fase de configuração.
Erro 6 : generateGitProperties falha em `FilterOutputStream.write()
A compilação Java passa. Os recursos são processados. Então:
> Task :generateGitProperties FAILED
No signature of method: java.io.FilterOutputStream.write() is applicable for argument types: (Integer) values: [103]
O plugin`gradle-git-properties`A versão 2.5.0 é incompatível com Java 21 / Gradle 9. Um bug interno tenta chamar`write(int)`via Groovy em um fluxo que ele fechou
Correçãoem`gradle.properties` :
gitPropertiesPluginVersion=2.5.7
Finalmente : Testcontainers entra em cena
Até aqui, era "build script debugging" puro. Cada erro era uma incompatibilidade do Gradle 9 ou Java 21 nos scripts de build. Depois das seis correções acima, o`./gradlew assemble`passa com sucesso.
mais`./gradlew build`executa também os testes, e os testes Cucumber usam o Testcontainers para iniciar um PostgreSQL efêmero.
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"}
O Testcontainers é incapaz de se comunicar com o Docker. Duas estratégias diferentes (variáveis de ambiente + socket Unix) falham com o mesmo erro.
@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 : novo DockerClient() DJ -> DE : GET /_ping\nUser-Agent: docker-java/3.4.1\nAPI-Version: 1.32 DE --> DJ : HTTP 400 Requisição Inválida\nversão do cliente 1.32 está muito antiga.\nMínimo suportado API versão é 1.40 DJ --> TC : BadRequestException TC --> TC : Não foi possível encontrar um ambiente válido\nDocker note right of DE Docker Motor 29.x impõe API mínimo = 1.40 (padrão antigo : 1.24) end note note left of DJ docker-java 3.4.1 envia hardcoded API version 1.32 no cabeçalho HTTP end note @enduml
Fase 1 : a pista docker-java
Primeiro reflexo: o cliente Docker Java usado pelo Testcontainers envia a versão da API`1.32`no seu handshake HTTP, mas o Docker Engine 29.4.1 exige um mínimo de`1.40`. O problema está no nível do cliente, não do daemon.
Verificação da versão do docker-java resolvida pelo 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, datando do Testcontainers 1.20.6. A última versão pública do docker‑java é a 3.5.1. Vamos tentar forçar a atualização da versão via`resolutionStrategy`no bloco`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")
}
}
}
}
Reexecução do build. Mesmo erro :`client version 1.32 is too old`.
Verificação do cache Gradle: os JARs foram realmente baixados novamente, mas o erro persiste. docker-java 3.5.1 continua enviando sempre`1.32`. Esta versão portanto NÃO é suficiente para Docker Engine 29.4.1.
Ação de purificaçãolimpar completamente o cache do Gradle, apenas para garantir:
rm -rf ~/.gradle/caches
rm -rf /path/to/project/.gradle
Recarga completa após purga. Mesmo erro.
Fase 2 : pesquisa web e trilha de navegação GitHub
docker‑java 3.5.1 é insuficiente. Não resta mais do que buscar se alguém encontrou esse problema exato.
Pesquisa direcionada nos problemas testcontainers-java e 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
Primeiros resultados imediatos :
-
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.*
O fio de Ariane está claro : Docker Engine 29.x elevou sua versão mínima da API para`1.24`(antigo defeito) à`1.40`. O Testcontainers 1.20.x usa o docker-java 3.4.x que negocia a versão da API enviando`1.32`. O daemon recusa polidamente mas firmemente
Na issue #11491, um mantenedor responde:
__ Olá, tenho um projeto em execução com a versão 2.0.3 num runner GH com engine 29.1 e funciona. Vocês podem verificar se não há conflito de versão por favor? Por favor, lembre-se de que todos os módulos foram prefixados com`testcontainers-. Assim, a partir da versão 2.x passamos de`postgresql to testcontainers-postgresql (empty)
Esta resposta contémduas informações críticas :
-
Testcontainers 2.0.3+ resolve o problema
-
Os módulos têm sidorenomeados com o prefixo `testcontainers-
Fase 3 : a armadilha dos artefatos renomeados
A mudança de nome é brutal. No Testcontainers 2.x, nenhum dos antigos nomes funciona com esse nome:
| Antigo nome (1.x) | Novo nome (2.x) |
|---|---|
|
|
|
|
|
|
|
inalterado |
Primeira tentativa: aplicar o BOM Testcontainers 2.0.5 e os novos nomes de artefatos em`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.
O BOM do testcontainers-bom não é suficiente.Por quê? Porque o Spring Boot 3.4.5 expõe seu próprio BOM de gerenciamento de dependências (spring-boot-dependencies) quemganhasobre o bom do Testcontainers na resolução transitiva. Spring Boot corrige`testcontainers` à 1.20.6, e portanto todos os artefatos que não estão explicitamente no BOM Spring Boot (como os novos`testcontainers-*) resolvem para os nomes antigos`1.20.6-- que não existem mais nesta versão.
Ao inspecionar o`dependencies --configuration testRuntimeClasspath`, on trouve :
+--- org.testcontainers:testcontainers -> 1.20.6 (*)
| \--- org.testcontainers:testcontainers:2.0.5 -> 1.20.6
Le →`indica a substituição: Testcontainers 2.0.5 está solicitado, mas o Spring Boot força`1.20.6.
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle\n(consumidor)" as BG #E3F2FD {
rectangle "pedido\n`testcontainers-bom:2.0.5`" as REQ
rectangle "pedido\n`testcontainers-postgresql`" as REQ2
}
component "Maven Central" as MC #FFF3E0
component "Declarações" as DEC #E8F5E9 {
rectangle "`testcontainers-bom:2.0.5`\nfixo 2.0.5" as BOM2
rectangle "`spring-boot-dependencies:3.4.5`\nfixo 1.20.6" as BOM1
}
component "Resolução Gradle" as RES #FFEBEE {
rectangle "Versão vencedora\n`testcontainers:1.20.6`" as WIN #EF9A9A
}
BG --> DEC : "analisa os BOMs"
BOM2 --> RES : "propõe 2.0.5"
BOM1 --> RES : "propõe 1.20.6"
WIN -> MC : "baixa\ntestcontainers:1.20.6"
note bottom of WIN
Spring Boot BOM principal car
o plugin spring-boot aplica
suas dependências de gerenciamento
depois da resolução do consumidor
end note
@enduml
Fase 4 : forçar a resolução
A estratégia passa a ser dupla
-
Forçar o docker-java para sua versão 3.7.1 (testada como compatível com Docker Engine 29)
-
Forçar Testcontainers core para 2.0.5 contornando o BOM do Spring Boot
@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle
left to right direction
component "build.gradle\n(com resolutionStrategy)" as BG #E8F5E9 {
rectangle "BOM Testcontainers\n`2.0.5`" as BOM2
rectangle "`resolutionStrategy\n.eachDependency`" as RS #66BB6A
}
component "Conflito resolvido" as RESOLVED #E3F2FD {
rectangle "docker-java\n`3.7.1` (forcado)" as DJ
rectangle "testcontainers\n`2.0.5` (forcado)" 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(implícito, ultrapassado)" as BOM1 #FFEBEE
component "Maven Central" as MC #FFF3E0
BG --> RESOLVED : "BOM Testcontainers 2.0.5\npassa diretamente"
RS -> DJ : "force 3.7.1"
RS -> TC : "force 2.0.5"
RESOLVED --> MC : "baixar os JARs\ncompatíveis Docker 29"
BOM1 -[#gray,dashed]-> RESOLVED : "ignorado no testcontainers\ne docker-java"
note bottom of RS
`eachDependency` intercepta
a resolução Gradle AVANT
que o BOM Spring Boot não ganha.
É uma intervenção cirúrgica.
end note
@enduml
Correção finalem`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`é o único meio de contornar o BOM`spring-boot-dependencies`Gerenciado pelo plugin Spring Boot. O teste de igualdade sobre`details.requested.name == "testcontainers"`é intencionalmente restritivo: só forçamos o módulo core. Os módulos`testcontainers-*`seguem o BOM do Testcontainers 2.0.5 que declaramos explicitamente
Resultado final
$ ./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 finalmente conseguem criar seus contêineres PostgreSQL via Docker Engine 29.4.1. O erro de negócio final (HTTP 500 no teste Cucumber) é um problema de aplicação totalmente independente do script de build.
Tabela resumo das correções
| # | Arquivo | Problema | Correção |
|---|---|---|---|
1 |
|
Variável`reportsDir`inexistente |
|
2 |
|
`main`descontinuado Gradle 9 |
|
3 |
|
`sourceCompatibility`top-level proibido Gradle 9 |
Bloco`java { sourceCompatibility = JavaVersion.VERSION_21 }` |
4 |
|
Asserção Groovy sintaticamente inválida |
|
5 |
|
Dependência implícita`compileKotlin`→ |
|
6 |
|
`gitPropertiesPluginVersion`Incompatível com o Java 21 |
|
7 |
Cache Gradle |
docker-java 3.5.1 testado, resolvido mas insuficiente |
Purga + passagem docker-java 3.7.1 |
8 |
|
Testcontainers 1.20.6 incompatível com o Docker Engine 29 |
Forçando Testcontainers 2.0.5 + prefix`testcontainers-*`+ resolutionStrategy vs Spring Boot BOM |
Conclusão
Se o seu build JHipster/Gradle falha após uma atualização para Gradle 9 + Docker Engine 29, os erros se dividem em duas categorias :
erros de script de build(as primeiras 6) : O Gradle 9 remove propriedades e sintaxes que vinham funcionando há anos (sourceCompatibility=, main =, reportsDir). São migrações mecânicas uma vez que se conhece a nova API.
Erros de tempo de execução do Docker(as 2 últimas) : Docker Engine 29.4.1 exclui clientes API < 1.40. Testcontainers 1.20.6 (imposto pelo Spring Boot 3.4.5) usa o docker-java 3.4.1 que envia 1.32. Somente Testcontainers 2.0.5 + docker-java 3.7.1 resolve o problema, com o renomeamento dos artefatos e a imposição da versão via`resolutionStrategy.eachDependency`como único meio de contornar o BOM implícito do Spring Boot.
O script de compilação Gradle na raiz do projeto agora está limpo.`./gradlew build`passa para o Java 21, Gradle 9.4.1, Spring Boot 3.4.5 e Docker Engine 29.4.1.