TL;DR: An in-memory H2 database and an embedded broker only prove that your code works against something like production. Testcontainers gives JUnit tests a real PostgreSQL and a real Kafka in Docker. With Spring Boot’s
@ServiceConnection, it takes about ten lines of config.
Generated AI image by Google Gemini Nano Banana
Introduction
We’ve all had this build: every test is green, the pipeline is fast, and the team ships with confidence. Then the first deploy hits a real PostgreSQL instance and falls over with ERROR: column "metadata" is of type jsonb but expression is of type character varying.
The tests weren’t lying, exactly. They were answering a different question. H2 told you your code works against H2.
I spent a long stretch of my career in Australian banking, where that gap matters a lot. A test that passes in memory and fails in production isn’t a flaky test. It’s an incident report. So as part of my Java comeback series, I’ve been rebuilding my integration-testing habits around Testcontainers: real infrastructure, started in Docker, managed by JUnit.
In this post, we’ll replace H2 and the embedded Kafka broker with the real thing. Whether you’re maintaining a Spring Boot service or just tired of “works on my H2,” you’ll find patterns you can apply on Monday.