tiempo de lectura : 15 minutes

Ya has conocido ese momento en el que un simple`./gradlew build`que lanzas cien veces al día se bloquea repentinamente ante un error nunca visto ? Multiplícalo por once errores sucesivos, agrega una buena dosis de incompatibilidad de Docker Engine 29, espolvorea con Gradle 9 que hace desaparecer las API que utilizábamos desde hace diez años. Aquí tienes el cuaderno de bitácora completo.


Diagrama 1 — Migración Gradle 9 (cadena de errores)

Objetivo: mostrar la serie de incompatibilidades.

👉 Claro, centrado en Gradle.
Failed to generate image: PlantUML preprocessing failed: [From <input> (line 5) ]

@startuml
left to right direction
title Migración Gradle 9
rectangle "1\nreportsDir" as A
rectangle "2
^^^^^
 Syntax Error? (Assumed diagram type: component)

@startuml
left to right direction
title Migración Gradle 9
rectangle "1\nreportsDir" as A
rectangle "2
clase principal" as B
rectangle "3\nsourceCompatibility" as C
rectangle "4\nGroovy assert" as D

A --> B
B --> C
C --> D

@enduml

Diagrama 2 — Plugins JHipster incompatibles

Objetivo: aislar los plugins rotos.

👉 Visión propia de las dependencias
Failed to generate image: PlantUML preprocessing failed: [From <input> (line 7) ]

@startuml
left to right direction
title Plugins a corregir
skinparam shadowing false
skinparam roundcorner 18

rectangle "1
^^^^^
 Syntax Error? (Assumed diagram type: class)

@startuml
left to right direction
title Plugins a corregir
skinparam shadowing false
skinparam roundcorner 18

rectangle "1
openApiGenerate" as A #FFF3E0
rectangle "2
gitProperties
2.5.7" as B #FFF3E0
rectangle "3
Versiones
corregidas" as C #E3F2FD
rectangle "4\nEnsamblar OK" as D #E8F5E9

A --> B
B --> C
C --> D

@enduml

Diagrama 3 — Docker 29 / Testcontainers

Objetivo: problema de tiempo de ejecución.

👉 Historia clara del runtime.
Failed to generate image: PlantUML preprocessing failed: [From <input> (line 12) ]

@startuml
left to right direction
title Docker 29 + Testcontainers

skinparam shadowing false
skinparam roundcorner 18
skinparam defaultFontSize 14

rectangle "1\nEnsambla OK" as A #E8F5E9
rectangle "2\nTests Cucumber FAIL" as B #FFEBEE
rectangle "3\nTestcontainers 1.32\nrechazado" as C #FFF3E0
rectangle "4
^^^^^
 Syntax Error? (Assumed diagram type: component)

@startuml
left to right direction
title Docker 29 + Testcontainers

skinparam shadowing false
skinparam roundcorner 18
skinparam defaultFontSize 14

rectangle "1\nEnsambla OK" as A #E8F5E9
rectangle "2\nTests Cucumber FAIL" as B #FFEBEE
rectangle "3\nTestcontainers 1.32\nrechazado" as C #FFF3E0
rectangle "4
Actualización
versiones
es
es
es
versiones" as D #E3F2FD
rectangle "5\nCompilación final OK" as E #E8F5E9

A --> B
B --> C
C --> D
D --> E

@enduml

otra manera de representárselo
Failed to generate image: PlantUML preprocessing failed: [From <input> (line 17) ]

@startuml


skinparam backgroundColor #FEFEFE
skinparam shadowing false
skinparam defaultFontName Inter
skinparam defaultFontSize 14
skinparam ArrowThickness 2
skinparam RoundCorner 18

title Roadmap de Estabilización — Gradle 9 hacia Build Final
left to right direction

rectangle "�⚙️ Migración Gradle 9" as A #E3F2FD
rectangle "Plugins JHipster" as B #FFF3E0
rectangle "🐳 Docker 29
^^^^^
 Syntax Error? (Assumed diagram type: component)

@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 Estabilización — Gradle 9 hacia Build Final
left to right direction

rectangle "�⚙️ Migración Gradle 9" as A #E3F2FD
rectangle "Plugins JHipster" as B #FFF3E0
rectangle "🐳 Docker 29
Testcontainers" as C #FCE4EC
rectangle "�✅ Build Estable" 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

La escena: un proyecto JHipster, una compilación que ya no carga

El proyecto`edster`es una aplicación JHipster generada en 2024. El script de build raíz`build.gradle`estuvo estable durante meses. Entonces deciden subir a una versión:

Componente

Versión objetivo

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)

El cambio de versión de Java se realiza mediante SDKMAN:

sdk use java 21.0.11-tem

Primer lanzamiento:

$ ./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'

No podemos ni siquiera iniciar la carga del script de compilación. El problema está en`buildSrc/src/main/groovy/jhipster.cucumber-conventions.gradle`.

Error 1 : la variable fantasma `reportsDir

El script`jhipster.cucumber-conventions.gradle`contiene:

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"
    // ...
}

La variable definida es`cucumberReportsDir`. La utilizada es`reportsDir`-- que no está definida en ninguna parte.

Corrección:`outputs.dir(cucumberReportsDir)`

Error 2: main = \"…​\" ya no existe en Gradle 9

Reinicio inmediato:

Could not set unknown property 'main' for task ':consoleLauncherTest' of type org.gradle.api.tasks.JavaExec.

Gradle 9 elimina la propiedad`main`en beneficio de`mainClass`sobre las tareas`JavaExec`.

Corrección : mainClass = "org.junit.platform.console.ConsoleLauncher"

Error 3: sourceCompatibility ya no es una propiedad del proyecto

Nuevo error:

Could not set unknown property 'sourceCompatibility' for root project 'edster' of type org.gradle.api.Project.

El script raíz tenía:

sourceCompatibility=17
targetCompatibility=17

Gradle 9 elimina estas propiedades a nivel raíz. Deben vivir en un bloque.java.

Corrección:

java {
    sourceCompatibility = JavaVersion.VERSION_21
    targetCompatibility = JavaVersion.VERSION_21
}

Error 4 : la afirmación de Groovy a tres operandos

El antiguo script contenía :

assert System.properties["java.specification.version"] == "17" || "21" || "24"

En Groovy,"21" et "24"`son strings truthy. La expresión se resuelve en(…​ == "17") || true || true`, lo cual es siempre cierto — pero la sintaxis no es válida para la afirmación estricta deseada.

Corrección:

assert System.properties["java.specification.version"] in ["17", "21", "23", "24"]

Error 5 : la dependencia implícita compileKotlin → `openApiGenerate

El build progresa. Compilación de Kotlin exitosa. Luego:

Task ':compileKotlin' uses this output of task ':openApiGenerate'
without declaring an explicit or implicit dependency.

Gradle 9 rechaza las dependencias implícitas entre tareas. Desde`compileKotlin`lee las fuentes generadas por`openApiGenerate`, hay que declarar el enlace

Correcciónen`build.gradle`:

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

Le `afterEvaluate`es necesario porque`openApiGenerate`es una extensión (plugin OpenAPI Generator) y no una tarea directamente accesible durante la fase de configuración.

Error 6 : generateGitProperties falla en `FilterOutputStream.write()

La compilación Java pasa. Los recursos son procesados. Entonces:

> Task :generateGitProperties FAILED

No signature of method: java.io.FilterOutputStream.write() is applicable for argument types: (Integer) values: [103]

El plugin`gradle-git-properties`la versión 2.5.0 es incompatible con Java 21 / Gradle 9. Un error interno intenta llamar`write(int)`a través de Groovy en un flujo que lo cerró.

Correcciónen`gradle.properties`:

gitPropertiesPluginVersion=2.5.7

Finalmente: Testcontainers entra en escena

Hasta ahora, era "build script debugging" puro. Cada error era una incompatibilidad de Gradle 9 o Java 21 en los scripts de construcción. Después de las seis correcciones anteriores, el`./gradlew assemble`pasa con éxito.

Pero`./gradlew build`ejecuta también las pruebas, y las pruebas de Cucumber utilizan Testcontainers para iniciar un 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"}

Testcontainers es incapaz de comunicarse con Docker. Dos estrategias diferentes (variables de entorno + socket Unix) fallan con el mismo error.

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 6) ]

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

actor "Testcontainers\n1.20.6" as TC
participant "docker-java
^^^^^
 Syntax Error? (Assumed diagram type: sequence)

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false

actor "Testcontainers\n1.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

Fase 1: la pista docker-java

Primer reflejo : el cliente Docker Java utilizado por Testcontainers envía la versión API`1.32`en su handshake HTTP, pero Docker Engine 29.4.1 requiere un mínimo de`1.40`. El problema está a nivel del cliente, no del daemon.

Verificación de la versión de docker-java resuelta por 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, de Testcontainers 1.20.6. La última versión pública de docker-java es 3.5.1. Intentemos forzar la actualización de la versión mediante`resolutionStrategy`en el bloque`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")
            }
        }
    }
}

Reinicio del build. Mismo error:`client version 1.32 is too old`.

Comprobación del caché de Gradle : los JARs han sido re-descargados correctamente, pero el error persiste. docker-java 3.5.1 sigue enviando`1.32`. Esta versión por lo tanto NO es suficiente para Docker Engine 29.4.1.

Acción de purificación: vaciar completamente el caché de Gradle, solo para estar seguro :

rm -rf ~/.gradle/caches
rm -rf /path/to/project/.gradle

Reinicio completo después de la purga. Mismo error.

Fase 2 : búsqueda web y camino de migas de pan GitHub

docker-java 3.5.1 es insuficiente. Solo queda buscar si alguien ha encontrado este problema exacto.

Búsqueda específica sobre los problemas testcontainers-java y 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

Primeros resultados inmediatos :

El rastro de migas es claro: Docker Engine 29.x ha elevado su versión mínima de API de`1.24`(antiguo defecto) a`1.40`. Testcontainers 1.20.x utiliza docker-java 3.4.x que negocia la versión de la API al enviar`1.32`. El daemon rechaza cortés pero firmemente.

En el issue #11491, un mantenedor responde:

__ Hola, tengo un proyecto en ejecución con la versión 2.0.3 en un runner de GH con motor 29.1 y funciona. ¿Podrían todos comprobar por favor que no haya ningún conflicto de versiones? Por favor, recuerda que todos los módulos estaban prefijados con`testcontainers-. Así que, empezando con la versión 2.x pasamos de`postgresql to testcontainers-postgresql (blank)

Esta respuesta contienedos informaciones críticas(empty)

  1. Testcontainers 2.0.3+ resuelve el problema

  2. Los módulos han sidorenombrados con el prefijo `testcontainers-

Fase 3 : la trampa de los artifacts renombrados

El renombramiento es brutal. En Testcontainers 2.x, ninguno de los antiguos nombres funciona bajo ese nombre :

Antiguo nombre (1.x)

Nuevo nombre (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

sin cambios

Primer intento: aplicar la BOM Testcontainers 2.0.5 y los nuevos nombres de artefactos en`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.

El BOM de testcontainers-bom no es suficiente.¿Por qué? Porque Spring Boot 3.4.5 expone su propio BOM de gestión de dependencias`spring-boot-dependencies`) quienganaen el BOM de Testcontainers en la resolución transitiva. Spring Boot establece`testcontainers` à 1.20.6, y por lo tanto todos los artefactos que no están explícitamente en el BOM de Spring Boot (como los nuevos`testcontainers-*) resuelven a los antiguos nombres`1.20.6-- que ya no existen en esta versión.

Al inspeccionar el`dependencies --configuration testRuntimeClasspath`, se encuentra :

+--- org.testcontainers:testcontainers -> 1.20.6 (*)
|    \--- org.testcontainers:testcontainers:2.0.5 -> 1.20.6

Le →`indica la sustitución: Testcontainers 2.0.5 es solicitado, pero Spring Boot fuerza`1.20.6.

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 17) ]

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle

left to right direction

component "build.gradle\n(consumidor)" as BG #E3F2FD {
    rectangle "solicitud\n`testcontainers-bom:2.0.5" as REQ
    rectangle "solicitud\n`testcontainers-postgresql" as REQ2
}

component "Maven Central" as MC #FFF3E0

component "Declaraciones" as DEC #E8F5E9 {
    rectangle "testcontainers-bom:2.0.5`\ncorrige 2.0.5" as BOM2
    rectangle "spring-boot-dependencies:3.4.5`
^^^^^
 Syntax Error? (Assumed diagram type: component)

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle

left to right direction

component "build.gradle\n(consumidor)" as BG #E3F2FD {
    rectangle "solicitud\n`testcontainers-bom:2.0.5" as REQ
    rectangle "solicitud\n`testcontainers-postgresql" as REQ2
}

component "Maven Central" as MC #FFF3E0

component "Declaraciones" as DEC #E8F5E9 {
    rectangle "testcontainers-bom:2.0.5`\ncorrige 2.0.5" as BOM2
    rectangle "spring-boot-dependencies:3.4.5`
fijo 1.20.6" as BOM1
}

component "Resolución Gradle" as RES #FFEBEE {
    rectangle "Versión ganadora\n`testcontainers:1.20.6" as WIN #EF9A9A
}

BG --> DEC : "analiza los BOMs"
BOM2 --> RES : "propone 2.0.5"
BOM1 --> RES : "propone 1.20.6"
WIN -> MC : "descarga
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

Fase 4 : forzar la resolución

La estrategia se vuelve doble:

  1. Forzar docker-java a su versión 3.7.1 (probada como compatible con Docker Engine 29)

  2. Forzar Testcontainers core a 2.0.5 evitando el BOM de Spring Boot

Failed to generate image: PlantUML preprocessing failed: [From <input> (line 8) ]

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle

left to right direction

component "build.gradle
^^^^^
 Syntax Error? (Assumed diagram type: class)

@startuml
skinparam backgroundColor #FEFEFE
skinparam handwritten false
skinparam componentStyle rectangle

left to right direction

component "build.gradle
(con resolutionStrategy)" as BG #E8F5E9 {
    rectangle "BOM Testcontainers
`2.0.5" as BOM2
    rectangle "resolutionStrategy\n.eachDependency" as RS #66BB6A
}

component "Conflicto resuelto" as RESOLVED #E3F2FD {
    rectangle "docker-java\n`3.7.1` (forzado)" as DJ
    rectangle "testcontainers
`2.0.5` (forzado)" 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\n(implícito, sobrescrito)" as BOM1 #FFEBEE

component "Maven Central" as MC #FFF3E0

BG --> RESOLVED : "BOM Testcontainers 2.0.5
pasa directamente"
RS -> DJ : "fuerza 3.7.1"
RS -> TC : "fuerza 2.0.5"
RESOLVED --> MC : "descarga los JARs
compatibles Docker 29"
BOM1 -[#gray,dashed]-> RESOLVED : "ignorado en 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

Corrección finalen`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`es el único modo de sobrepasar el BOM`spring-boot-dependencies`gestionado por el plugin Spring Boot. La prueba de igualdad sobre`details.requested.name == "testcontainers"`es voluntariamente restrictivo: solo forzamos el módulo core. Los módulos`testcontainers-*`siguen el BOM de Testcontainers 2.0.5 que hemos declarado explícitamente.

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 logran crear sus contenedores PostgreSQL mediante Docker Engine 29.4.1. El error de negocio final (HTTP 500 en la prueba Cucumber) es un problema de aplicación completamente independiente del script de compilación.

Resumen de correcciones

# Archivo Problema Corrección

1

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

Variable`reportsDir`inexistente

outputs.dir(cucumberReportsDir)

2

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

`main`obsoleto Gradle 9

mainClass

3

build.gradle

`sourceCompatibility`top-level prohibido Gradle 9

bloque`java { sourceCompatibility = JavaVersion.VERSION_21 }`

4

build.gradle

Aserción Groovy sintácticamente inválida

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

5

build.gradle

Dependencia implícita`compileKotlin` → openApiGenerate

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

6

gradle.properties

`gitPropertiesPluginVersion`incompatible Java 21

2.5.7

7

Caché Gradle

docker-java 3.5.1 probado, resuelto pero insuficiente

Purga + paso docker-java 3.7.1

8

build.gradle

Testcontainers 1.20.6 incompatible con Docker Engine 29

Forzamiento Testcontainers 2.0.5 + prefijo`testcontainers-*`+ resoluciónStrategy frente a Spring Boot BOM

Conclusión

Si tu build JHipster/Gradle falla después de una actualización a Gradle 9 + Docker Engine 29, los errores se dividen en dos categorías :

Errores de script de construcción(las 6 primeras): Gradle 9 elimina propiedades y sintaxis que funcionaban desde hace años (sourceCompatibility=, main =, reportsDir). Son migraciones mecánicas una vez que se conoce la nueva API.

Errores de tiempo de ejecución Docker(las 2 últimas): Docker Engine 29.4.1 descarta los clientes API < 1.40. Testcontainers 1.20.6 (impuesto por Spring Boot 3.4.5) utiliza docker-java 3.4.1 que envía 1.32. Solo Testcontainers 2.0.5 + docker-java 3.7.1 resuelve el problema, con el renombrado de los artefactos y el forzado de la versión via`resolutionStrategy.eachDependency`como única forma de evitar el BOM implícito de Spring Boot.

El script de build de Gradle en la raíz del proyecto ahora está limpio.`./gradlew build`pasa a Java 21, Gradle 9.4.1, Spring Boot 3.4.5 y Docker Engine 29.4.1.

Articles connexes