Add testcontainers2 module: Testcontainers 2.0 artifact renames, JUnit 4 removal, and the Docker API version bump

Verifies five claims against primary sources rather than release-note prose: the
org.testcontainers:junit-jupiter -> testcontainers-junit-jupiter rename (and the other 60 like
it, per OpenRewrite's own Testcontainers2Migration recipe), that GenericContainer no longer
implements org.junit.rules.TestRule anywhere in the 2.0.5 jar (confirmed exhaustively, not just
for this one class), the real compiler/dependency-resolution breaks from bumping only a version
number on existing JUnit 4 @ClassRule code, the literal 1.32 -> 1.44 default Docker API version
bump in DockerClientProviderStrategy's bytecode (tied to the real Docker Engine 29 compatibility
issue), and Spring Boot 4.1.1's BOM pinning 2.0.5. Built and tested with no Docker daemon
available; one test class demonstrates a clean @EnabledIfDockerAvailable skip, guarded at the
class level after the method-level guard was caught failing first.
This commit is contained in:
2026-10-08 13:19:23 +00:00
parent 2b2fbe54a2
commit fd3434c42d
11 changed files with 597 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
# testcontainers2
Companion module for [Migrating to Testcontainers 2.0: Artifact Renames, Package Moves and JUnit 6](https://ankurm.com/) on ankurm.com.
## What this is
Testcontainers 2.0 shipped on 2025-10-14 as a deliberately disruptive release: 61 artifacts
renamed, JUnit 4 support dropped from the type hierarchy itself (not deprecated — removed), and
a default Docker API version bump that quietly fixes a real compatibility break with current
Docker Engine releases. This module verifies each of those claims against a real jar, a real
Maven Central artifact history, or a real compiler run — never against a changelog's prose alone
— and runs the result against real JUnit 6.1.3. No `docs/NN-topic.md` chapters; everything beyond
what fits in the post itself is in the Javadoc on the two committed test classes and in the
captured transcripts below.
**This module was built and tested in a sandbox with no Docker daemon available.** Both test
classes run (or cleanly skip) without one; nothing here asserts on a real container's output.
**Self-correction made before this module was finished:** the first draft of
`NewPostgresContainerSkipsCleanlyTest` put `@EnabledIfDockerAvailable` on the test *method*. That
compiles, and it is wrong — see the Javadoc on that class and
`docs/output/01-guard-on-method-still-fails.txt` for the real failure it produced before the fix.
## Versions
| Dependency | Version | Verified against |
|---|---|---|
| Testcontainers | 2.0.5 | `testcontainers-bom-2.0.5.pom`; same version `data-jpa-testcontainers` verified Spring Boot 4.1.1's own BOM pins |
| JUnit Jupiter | 6.1.3 | `maven-metadata.xml`, `<release>` |
| AssertJ | 3.27.7 | `maven-metadata.xml`, `<release>` |
| JUnit 4 (test-scope only, for the reflective check) | 4.13.2 | pinned deliberately — see `GenericContainerNoLongerImplementsTestRuleTest` |
| JDK | 25 (Temurin) | `java -version`, this module's own build |
## Quickstart
```bash
mvn test # 3 tests run; 1 skips cleanly, 0 failures
```
## Test classes
| Class | What it proves | Needs Docker | Outcome here |
|---|---|---|---|
| `GenericContainerNoLongerImplementsTestRuleTest` | `GenericContainer` implemented `org.junit.rules.TestRule` by inheritance in 1.21.4; zero classes anywhere in the 2.0.5 core jar implement it | No | Pass |
| `NewPostgresContainerSkipsCleanlyTest` | The relocated, non-generic `org.testcontainers.postgresql.PostgreSQLContainer` compiles and runs under JUnit 6, guarded correctly (class-level) by `@EnabledIfDockerAvailable` | Yes (guarded) | Skipped |
## Captured output
| File | Contents |
|---|---|
| `docs/output/00-full-test-run.txt` | `mvn clean test` — 3 tests, 1 clean skip, build green, with the guard on the class |
| `docs/output/01-guard-on-method-still-fails.txt` | The same class with `@EnabledIfDockerAvailable` on the method instead — a real `IllegalStateException` from `GenericContainer.start()`, because `TestcontainersExtension.beforeAll` starts every `@Container` field before any method-level condition runs |
| `docs/output/02-genericcontainer-testrule-removed-javap.txt` | `javap` proof of the `TestRule` removal: present via inheritance in 1.21.4, absent — exhaustively, jar-wide — in 2.0.5 |
| `docs/output/03-docker-api-version-bump-javap.txt` | `javap` proof that the hardcoded default Docker API version moved from 1.32 to 1.44 between 1.21.4 and 2.0.5, tied to the real GitHub issue where Docker Engine 29 rejects API 1.32 outright |
| `docs/output/04-legacy-pom-compiles-under-1.21.4.txt` | A classic `@ClassRule public static PostgreSQLContainer<?>` JUnit 4 test, compiling clean against 1.21.4 with no extra dependency |
| `docs/output/05-legacy-pom-fails-to-resolve-under-2.0.5.txt` | The identical source file; only the version property changed to 2.0.5. The build fails before it even reaches the `TestRule` question, because `org.testcontainers:postgresql` has no 2.0.5 release at all |
| `docs/output/06-maven-central-artifact-history.txt` | The real `maven-metadata.xml` for both `org.testcontainers:junit-jupiter` (frozen at 1.21.4) and `org.testcontainers:testcontainers-junit-jupiter` (starts at 2.0.0) — the permanent fork point behind the rename |
## What's sourced from documentation, not run here
The full 61-artifact rename list and the composite `Testcontainers2Migration` OpenRewrite recipe
are quoted from OpenRewrite's own published recipe catalog, not executed in this sandbox — see
the post for the exact quotes and the link. Everything else above was compiled, run, or pulled
directly from a real jar or a real Maven Central response in this sitting.
@@ -0,0 +1,48 @@
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.ankurm:testcontainers2 >---------------------
[INFO] Building testcontainers2 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ testcontainers2 ---
[INFO] Deleting /home/claude/JUnit_Tutorials/testcontainers2/target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ testcontainers2 ---
[INFO] skip non existing resourceDirectory /home/claude/JUnit_Tutorials/testcontainers2/src/main/resources
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ testcontainers2 ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ testcontainers2 ---
[INFO] skip non existing resourceDirectory /home/claude/JUnit_Tutorials/testcontainers2/src/test/resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ testcontainers2 ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 2 source files with javac [debug release 25] to target/test-classes
[INFO]
[INFO] --- surefire:3.6.0:test (default-test) @ testcontainers2 ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[INFO] Running com.ankurm.tutorials.junit.testcontainers2.NewPostgresContainerSkipsCleanlyTest
[WARNING] Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.037 s -- in com.ankurm.tutorials.junit.testcontainers2.NewPostgresContainerSkipsCleanlyTest
[INFO] Running com.ankurm.tutorials.junit.testcontainers2.GenericContainerNoLongerImplementsTestRuleTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.166 s -- in com.ankurm.tutorials.junit.testcontainers2.GenericContainerNoLongerImplementsTestRuleTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 1
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.007 s
[INFO] Finished at: 2026-10-08T18:47:18+05:30
[INFO] ------------------------------------------------------------------------
@@ -0,0 +1,107 @@
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.ankurm:testcontainers2 >---------------------
[INFO] Building testcontainers2 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ testcontainers2 ---
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ testcontainers2 ---
[INFO] skip non existing resourceDirectory /home/claude/JUnit_Tutorials/testcontainers2/src/main/resources
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ testcontainers2 ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ testcontainers2 ---
[INFO] skip non existing resourceDirectory /home/claude/JUnit_Tutorials/testcontainers2/src/test/resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ testcontainers2 ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 2 source files with javac [debug release 25] to target/test-classes
[INFO]
[INFO] --- surefire:3.6.0:test (default-test) @ testcontainers2 ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit-platform/3.6.0/surefire-junit-platform-3.6.0.pom
Progress (1): 833 B␍Progress (1): 3.9 kB␍Progress (1): 4.6 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit-platform/3.6.0/surefire-junit-platform-3.6.0.pom (4.6 kB at 9.3 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-providers/3.6.0/surefire-providers-3.6.0.pom
Progress (1): 872 B␍Progress (1): 1.8 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-providers/3.6.0/surefire-providers-3.6.0.pom (1.8 kB at 26 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/common-java5/3.6.0/common-java5-3.6.0.pom
Progress (1): 844 B␍Progress (1): 2.4 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/common-java5/3.6.0/common-java5-3.6.0.pom (2.4 kB at 49 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.14.4/junit-platform-engine-1.14.4.pom
Progress (1): 953 B␍Progress (1): 3.2 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.14.4/junit-platform-engine-1.14.4.pom (3.2 kB at 37 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.14.4/junit-platform-commons-1.14.4.pom
Progress (1): 967 B␍Progress (1): 2.9 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.14.4/junit-platform-commons-1.14.4.pom (2.9 kB at 63 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-launcher/1.14.4/junit-platform-launcher-1.14.4.pom
Progress (1): 968 B␍Progress (1): 3.0 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-launcher/1.14.4/junit-platform-launcher-1.14.4.pom (3.0 kB at 73 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit-platform/3.6.0/surefire-junit-platform-3.6.0.jar
Progress (1): 7.7/57 kB␍Progress (1): 16/57 kB ␍Progress (1): 32/57 kB␍Progress (1): 33/57 kB␍Progress (1): 49/57 kB␍Progress (1): 57 kB ␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit-platform/3.6.0/surefire-junit-platform-3.6.0.jar (57 kB at 1.1 MB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/common-java5/3.6.0/common-java5-3.6.0.jar
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.14.4/junit-platform-engine-1.14.4.jar
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.14.4/junit-platform-commons-1.14.4.jar
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-launcher/1.14.4/junit-platform-launcher-1.14.4.jar
Progress (1): 7.7/272 kB␍Progress (1): 8.2/272 kB␍Progress (1): 25/272 kB ␍Progress (1): 36/272 kB␍Progress (1): 52/272 kB␍Progress (1): 69/272 kB␍Progress (1): 85/272 kB␍Progress (1): 101/272 kB␍Progress (1): 118/272 kB␍Progress (1): 134/272 kB␍Progress (1): 151/272 kB␍Progress (1): 159/272 kB␍Progress (1): 175/272 kB␍Progress (1): 192/272 kB␍Progress (1): 200/272 kB␍Progress (1): 216/272 kB␍Progress (1): 233/272 kB␍Progress (1): 249/272 kB␍Progress (1): 265/272 kB␍Progress (1): 272 kB ␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.14.4/junit-platform-engine-1.14.4.jar (272 kB at 2.6 MB/s)
Progress (1): 7.7/16 kB␍Progress (1): 11/16 kB ␍Progress (1): 16 kB ␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/common-java5/3.6.0/common-java5-3.6.0.jar (16 kB at 102 kB/s)
Progress (1): 7.7/164 kB␍Progress (1): 16/164 kB ␍Progress (2): 16/164 kB | 7.7/223 kB␍Progress (2): 16/164 kB | 16/223 kB ␍Progress (2): 16/164 kB | 16/223 kB␍Progress (2): 16/164 kB | 25/223 kB␍Progress (2): 16/164 kB | 33/223 kB␍Progress (2): 16/164 kB | 41/223 kB␍Progress (2): 32/164 kB | 41/223 kB␍Progress (2): 49/164 kB | 41/223 kB␍Progress (2): 57/164 kB | 41/223 kB␍Progress (2): 65/164 kB | 41/223 kB␍Progress (2): 73/164 kB | 41/223 kB␍Progress (2): 73/164 kB | 49/223 kB␍Progress (2): 73/164 kB | 57/223 kB␍Progress (2): 73/164 kB | 66/223 kB␍Progress (2): 73/164 kB | 74/223 kB␍Progress (2): 81/164 kB | 74/223 kB␍Progress (2): 98/164 kB | 74/223 kB␍Progress (2): 98/164 kB | 90/223 kB␍Progress (2): 114/164 kB | 90/223 kB␍Progress (2): 114/164 kB | 98/223 kB␍Progress (2): 114/164 kB | 106/223 kB␍Progress (2): 131/164 kB | 106/223 kB␍Progress (2): 147/164 kB | 106/223 kB␍Progress (2): 163/164 kB | 106/223 kB␍Progress (2): 164 kB | 106/223 kB ␍Progress (2): 164 kB | 115/223 kB␍Progress (2): 164 kB | 131/223 kB␍Progress (2): 164 kB | 147/223 kB␍Progress (2): 164 kB | 163/223 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.14.4/junit-platform-commons-1.14.4.jar (164 kB at 918 kB/s)
Progress (1): 171/223 kB␍Progress (1): 180/223 kB␍Progress (1): 196/223 kB␍Progress (1): 212/223 kB␍Progress (1): 223 kB ␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-launcher/1.14.4/junit-platform-launcher-1.14.4.jar (223 kB at 1.2 MB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/vintage/junit-vintage-engine/6.1.3/junit-vintage-engine-6.1.3.pom
Progress (1): 956 B␍Progress (1): 3.3 kB␍Progress (1): 3.5 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/vintage/junit-vintage-engine/6.1.3/junit-vintage-engine-6.1.3.pom (3.5 kB at 90 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/junit/vintage/junit-vintage-engine/6.1.3/junit-vintage-engine-6.1.3.jar
Progress (1): 7.7/81 kB␍Progress (1): 16/81 kB ␍Progress (1): 24/81 kB␍Progress (1): 32/81 kB␍Progress (1): 49/81 kB␍Progress (1): 65/81 kB␍Progress (1): 81/81 kB␍Progress (1): 81 kB ␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/vintage/junit-vintage-engine/6.1.3/junit-vintage-engine-6.1.3.jar (81 kB at 1.6 MB/s)
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.ankurm.tutorials.junit.testcontainers2.NewPostgresContainerSkipsCleanlyTest
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.676 s <<< FAILURE! -- in com.ankurm.tutorials.junit.testcontainers2.NewPostgresContainerSkipsCleanlyTest
[ERROR] com.ankurm.tutorials.junit.testcontainers2.NewPostgresContainerSkipsCleanlyTest.initializationError -- Time elapsed: 1.676 s <<< ERROR!
java.lang.IllegalStateException: Could not find a valid Docker environment. Please see logs and check configuration
at org.testcontainers.dockerclient.DockerClientProviderStrategy.lambda$getFirstValidStrategy$7(DockerClientProviderStrategy.java:274)
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
at org.testcontainers.dockerclient.DockerClientProviderStrategy.getFirstValidStrategy(DockerClientProviderStrategy.java:265)
at org.testcontainers.DockerClientFactory.getOrInitializeStrategy(DockerClientFactory.java:154)
at org.testcontainers.DockerClientFactory.client(DockerClientFactory.java:196)
at org.testcontainers.DockerClientFactory$1.getDockerClient(DockerClientFactory.java:108)
at com.github.dockerjava.api.DockerClientDelegate.authConfig(DockerClientDelegate.java:111)
at org.testcontainers.containers.GenericContainer.start(GenericContainer.java:316)
at org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.start(TestcontainersExtension.java:276)
at org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.access$200(TestcontainersExtension.java:263)
at org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$startContainers$4(TestcontainersExtension.java:83)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$startContainers$5(TestcontainersExtension.java:83)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
at org.testcontainers.junit.jupiter.TestcontainersExtension.startContainers(TestcontainersExtension.java:83)
at org.testcontainers.junit.jupiter.TestcontainersExtension.beforeAll(TestcontainersExtension.java:57)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
[INFO] Running com.ankurm.tutorials.junit.testcontainers2.GenericContainerNoLongerImplementsTestRuleTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.197 s -- in com.ankurm.tutorials.junit.testcontainers2.GenericContainerNoLongerImplementsTestRuleTest
[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR] NewPostgresContainerSkipsCleanlyTest.initializationError ? IllegalState Could not find a valid Docker environment. Please see logs and check configuration
[INFO]
[ERROR] Tests run: 3, Failures: 0, Errors: 1, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.778 s
[INFO] Finished at: 2026-10-08T18:46:46+05:30
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.6.0:test (default-test) on project testcontainers2:
[ERROR]
[ERROR] See /home/claude/JUnit_Tutorials/testcontainers2/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
@@ -0,0 +1,48 @@
# unzipped from testcontainers-1.21.4.jar
$ javap -p org/testcontainers/containers/GenericContainer.class
public class org.testcontainers.containers.GenericContainer<SELF extends org.testcontainers.containers.GenericContainer<SELF>>
extends org.testcontainers.containers.FailureDetectingExternalResource
implements org.testcontainers.containers.Container<SELF>, java.lang.AutoCloseable,
org.testcontainers.containers.wait.strategy.WaitStrategyTarget,
org.testcontainers.lifecycle.Startable {
...
}
$ javap -p org/testcontainers/containers/FailureDetectingExternalResource.class
public class org.testcontainers.containers.FailureDetectingExternalResource
implements org.junit.rules.TestRule {
public org.testcontainers.containers.FailureDetectingExternalResource();
...
}
# So in 1.21.4: GenericContainer -> FailureDetectingExternalResource -> implements TestRule.
# Every container class is a JUnit 4 TestRule by inheritance, with no extra dependency.
# unzipped from testcontainers-2.0.5.jar instead
$ javap -p org/testcontainers/containers/GenericContainer.class
public class org.testcontainers.containers.GenericContainer<SELF extends org.testcontainers.containers.GenericContainer<SELF>>
implements org.testcontainers.containers.Container<SELF>, java.lang.AutoCloseable,
org.testcontainers.containers.wait.strategy.WaitStrategyTarget,
org.testcontainers.lifecycle.Startable {
...
}
# No "extends FailureDetectingExternalResource" at all - that class and the TestRule contract
# it carried are both gone from the 2.0.5 jar. Confirmed exhaustively, not just for this one
# class:
$ mkdir ext2 && cd ext2 && unzip -oq ../testcontainers-2.0.5.jar
$ grep -rl "org/junit/rules/TestRule" . --include="*.class" | wc -l
0
# Compare: the same search against the 1.21.4 jar
$ mkdir ext1 && cd ext1 && unzip -oq ../testcontainers-1.21.4.jar
$ grep -rl "org/junit/rules/TestRule" . --include="*.class" | wc -l
2
# Zero classes anywhere in the 2.0.5 core jar reference org.junit.rules.TestRule. This is not a
# deprecation - the interface implementation is physically absent from the compiled jar. It is
# also why the fix is not "add the testcontainers-junit-jupiter dependency": that artifact is a
# JUnit 5 extension (@Testcontainers/@Container), not a JUnit 4 rule shim. There is no JUnit 4
# path forward in 2.0 - see GitHub release notes, testcontainers-java#10805 "Remove JUnit 4
# support" and #10808 "Fix missing junit4 leftovers", https://github.com/testcontainers/testcontainers-java/releases/tag/2.0.0
@@ -0,0 +1,37 @@
# unzipped from testcontainers-1.21.4.jar
$ javap -v -p org/testcontainers/dockerclient/DockerClientProviderStrategy.class
...
#773 = Utf8 Fallback to Docker API version 1.32.
#774 = String #773 // Fallback to Docker API version 1.32.
#775 = Utf8 VERSION_1_32
#777 = Fieldref org/testcontainers/shaded/com/github/dockerjava/core/RemoteApiVersion.VERSION_1_32:...
...
273: ldc_w #774 // String Fallback to Docker API version 1.32.
286: getstatic #777 // Field ...RemoteApiVersion.VERSION_1_32:...
# unzipped from testcontainers-2.0.5.jar instead
$ javap -v -p org/testcontainers/dockerclient/DockerClientProviderStrategy.class
...
#742 = Utf8 VERSION_1_44
#744 = Fieldref org/testcontainers/shaded/com/github/dockerjava/core/RemoteApiVersion.VERSION_1_44:...
#764 = Utf8 Pinging Docker API version 1.44.
#765 = String #764 // Pinging Docker API version 1.44.
...
208: getstatic #744 // Field ...RemoteApiVersion.VERSION_1_44:...
244: ldc_w #765 // String Pinging Docker API version 1.44.
# The hardcoded default Docker Engine API version this client falls back to moved from 1.32 to
# 1.44 somewhere between 1.21.4 and 2.0.5. The release notes for 2.0.2 say exactly that:
# "Set default docker API version to 1.44 (#11216) @eddumelendez" -
# https://github.com/testcontainers/testcontainers-java/releases/tag/2.0.2
#
# This is not a cosmetic bump. Docker Engine 29 (shipped inside Docker Desktop 4.52) raises its
# own *minimum accepted* client API version to 1.44, and rejects anything older outright:
# "client version 1.32 is too old. Minimum supported API version is 1.44, please upgrade
# your client to a newer version."
# reported against Testcontainers 1.21.3 in
# https://github.com/testcontainers/testcontainers-java/issues/11235 - closed as a duplicate,
# with multiple commenters confirming Testcontainers 2.0.2 works against the same Docker Desktop
# version where 1.21.x fails outright. A project pinned to the 1.x line has three options: set
# api.version=1.44 by hand in a ~/.docker-java.properties file, keep Docker Engine below 29, or move
# to 2.0.2+, where this is just the default.
@@ -0,0 +1,43 @@
Module: /tmp/legacy-check (a throwaway Maven project, not part of this repo - built only to
capture these two transcripts; see the post for why).
pom.xml dependencies at this point:
junit:junit:4.13.2
org.testcontainers:testcontainers:1.21.4
org.testcontainers:postgresql:1.21.4
src/test/java/com/ankurm/legacy/ClassicJUnit4RuleTest.java:
@ClassRule
public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17");
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.ankurm:legacy-check >-----------------------
[INFO] Building legacy-check 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ legacy-check ---
[INFO] Deleting /tmp/post46/legacy-check/target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ legacy-check ---
[INFO] skip non existing resourceDirectory /tmp/post46/legacy-check/src/main/resources
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ legacy-check ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ legacy-check ---
[INFO] skip non existing resourceDirectory /tmp/post46/legacy-check/src/test/resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ legacy-check ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 1 source file with javac [debug target 21] to target/test-classes
[WARNING] location of system modules is not set in conjunction with -source 21
not setting the location of system modules may lead to class files that cannot run on JDK 21
--release 21 is recommended instead of -source 21 -target 21 because it sets the location of system modules automatically
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.016 s
[INFO] Finished at: 2026-10-08T18:40:55+05:30
[INFO] ------------------------------------------------------------------------
@@ -0,0 +1,36 @@
Same module, same source file, zero code changes. The ONLY edit between this run and
04-legacy-pom-compiles-under-1.21.4.txt is the version property:
- <testcontainers.version>1.21.4</testcontainers.version>
+ <testcontainers.version>2.0.5</testcontainers.version>
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.ankurm:legacy-check >-----------------------
[INFO] Building legacy-check 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.20/jackson-annotations-2.20.pom
Progress (1): 952 B␍Progress (1): 2.3 kB␍Progress (1): 3.8 kB␍Progress (1): 6.4 kB␍Progress (1): 7.0 kB␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.20/jackson-annotations-2.20.pom (7.0 kB at 15 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/testcontainers/postgresql/2.0.5/postgresql-2.0.5.pom
[WARNING] The POM for org.testcontainers:postgresql:jar:2.0.5 is missing, no dependency information available
Downloading from central: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.20/jackson-annotations-2.20.jar
Progress (1): 7.7/79 kB␍Progress (1): 8.2/79 kB␍Progress (1): 25/79 kB ␍Progress (1): 41/79 kB␍Progress (1): 49/79 kB␍Progress (1): 57/79 kB␍Progress (1): 66/79 kB␍Progress (1): 74/79 kB␍Progress (1): 79 kB ␍ ␍Downloaded from central: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.20/jackson-annotations-2.20.jar (79 kB at 990 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/testcontainers/postgresql/2.0.5/postgresql-2.0.5.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.299 s
[INFO] Finished at: 2026-10-08T18:41:04+05:30
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project legacy-check: Could not resolve dependencies for project com.ankurm:legacy-check:jar:1.0.0
[ERROR] dependency: org.testcontainers:postgresql:jar:2.0.5 (compile)
[ERROR] Could not find artifact org.testcontainers:postgresql:jar:2.0.5 in central (https://repo.maven.apache.org/maven2)
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException
@@ -0,0 +1,47 @@
$ curl -sS https://repo1.maven.org/maven2/org/testcontainers/junit-jupiter/maven-metadata.xml
<metadata>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<versioning>
<latest>1.21.4</latest>
<release>1.21.4</release>
<versions>
... (1.10.0 through 1.21.4, 56 releases total) ...
<version>1.21.4</version>
</versions>
<lastUpdated>20251216004330</lastUpdated>
</versioning>
</metadata>
$ curl -sS https://repo1.maven.org/maven2/org/testcontainers/testcontainers-junit-jupiter/maven-metadata.xml
<metadata>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<versioning>
<latest>2.0.5</latest>
<release>2.0.5</release>
<versions>
<version>2.0.0</version>
<version>2.0.1</version>
<version>2.0.2</version>
<version>2.0.3</version>
<version>2.0.4</version>
<version>2.0.5</version>
</versions>
<lastUpdated>20260420182027</lastUpdated>
</versioning>
</metadata>
# The old artifactId's release history simply stops at 1.21.4 - no 2.x version was ever
# published under org.testcontainers:junit-jupiter. The new artifactId's history starts fresh
# at 2.0.0. This is the real, permanent fork point on Maven Central: "upgrade the version
# number" on your existing <artifactId>junit-jupiter</artifactId> dependency has no 2.x version
# to resolve to - it will stay on 1.21.4 forever, or fail outright for a module whose own
# artifactId was retired rather than just renamed (see 05-legacy-pom-fails-to-resolve-under-2.0.5.txt,
# where this happens to org.testcontainers:postgresql).
#
# This is one rename out of 61: OpenRewrite's own migration recipe documents the full list at
# https://docs.openrewrite.org/recipes/java/testing/testcontainers/testcontainers2dependencies -
# every org.testcontainers:<module> artifact gets a testcontainers-<module> artifactId, same
# groupId, same Maven Central repository. The core artifact, plain org.testcontainers:testcontainers,
# is the one name on the list that did NOT change.
+83
View File
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ankurm</groupId>
<artifactId>testcontainers2</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>testcontainers2</name>
<description>Migrating to Testcontainers 2.0: the artifact rename, the package move, the dropped
JUnit 4 support, and the Docker API version bump that quietly ships alongside all of it.</description>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.jupiter.version>6.1.3</junit.jupiter.version>
<testcontainers.version>2.0.5</testcontainers.version>
<assertj.version>3.27.7</assertj.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<!-- Core artifact: name unchanged by the 2.0 rename ("testcontainers", not "testcontainers-testcontainers"). -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<!-- Renamed: org.testcontainers:junit-jupiter -> org.testcontainers:testcontainers-junit-jupiter. -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<!-- Renamed: org.testcontainers:postgresql -> org.testcontainers:testcontainers-postgresql.
The old artifactId has no 2.0.5 release at all - see docs/output/05. -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<!-- JUnit 4's TestRule interface, needed only so GenericContainerNoLongerImplementsTestRuleTest
can reflectively check for it. Testcontainers 2.0 itself has no JUnit 4 dependency at all -
that is exactly the fact this module demonstrates. -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,44 @@
package com.ankurm.tutorials.junit.testcontainers2;
import org.junit.jupiter.api.Test;
import org.junit.rules.TestRule;
import org.testcontainers.containers.GenericContainer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Testcontainers 1.x's JUnit 4 integration was never a separate opt-in extension - it was built
* into the container classes themselves. {@code GenericContainer} extended
* {@code FailureDetectingExternalResource}, which implemented {@code org.junit.rules.TestRule}
* directly, confirmed on the real 1.21.4 jar with:
*
* <pre>
* $ javap -p org/testcontainers/containers/FailureDetectingExternalResource.class
* public class org.testcontainers.containers.FailureDetectingExternalResource
* implements org.junit.rules.TestRule
* </pre>
*
* That is why a plain {@code @ClassRule public static PostgreSQLContainer&lt;?&gt; postgres = ...}
* compiled with zero extra dependency under 1.21.4 - see
* docs/output/04-legacy-pom-compiles-under-1.21.4.txt. Testcontainers 2.0's release notes say
* "Remove JUnit 4 support" (testcontainers-java#10805); this test checks what that actually means
* for the type hierarchy, on the real 2.0.5 jar, rather than taking the changelog's word for it.
*/
class GenericContainerNoLongerImplementsTestRuleTest {
@Test
void genericContainerDoesNotImplementTestRule() {
assertThat(TestRule.class.isAssignableFrom(GenericContainer.class)).isFalse();
}
@Test
void noSuperclassOfGenericContainerImplementsTestRuleEither() {
Class<?> walk = GenericContainer.class;
while (walk != null) {
assertThat(TestRule.class.isAssignableFrom(walk))
.describedAs("%s should not implement TestRule in Testcontainers 2.0", walk.getName())
.isFalse();
walk = walk.getSuperclass();
}
}
}
@@ -0,0 +1,40 @@
package com.ankurm.tutorials.junit.testcontainers2;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.EnabledIfDockerAvailable;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The relocated class: {@code org.testcontainers.postgresql.PostgreSQLContainer}, confirmed on the
* real 2.0.5 jar in docs/output/02 of the companion {@code data-jpa-testcontainers} module from the
* previous post. It dropped its self-referential generic - {@code PostgreSQLContainer}, not
* {@code PostgreSQLContainer<?>} - which is a real compile-time change, not a cosmetic one.
*
* <p>{@code @EnabledIfDockerAvailable} is new in Testcontainers 2.0's JUnit 5 extension
* ({@code testcontainers-junit-jupiter}); it did not exist in the 1.x {@code junit-jupiter}
* artifact. The first draft of this class put the annotation on the test <em>method</em>, which
* compiles but does nothing useful: {@code TestcontainersExtension.beforeAll} starts every
* {@code @Container} field before any method-level condition is even evaluated, so the real
* failure in docs/output/01-guard-on-method-still-fails.txt happened before the guard had a
* chance to run. The annotation has to sit on the <em>class</em>, which is what gates container
* startup itself. With it there, this sandbox's missing Docker daemon produces a clean
* {@code Skipped: 1} instead of an error - see docs/output/00-full-test-run.txt.</p>
*/
@Testcontainers
@EnabledIfDockerAvailable
class NewPostgresContainerSkipsCleanlyTest {
@Container
static GenericContainer<?> postgres = new PostgreSQLContainer("postgres:17");
@Test
void containerIsConfiguredWithTheRelocatedNonGenericClass() {
assertThat(postgres.getDockerImageName()).isEqualTo("postgres:17");
assertThat(postgres).isInstanceOf(PostgreSQLContainer.class);
}
}