skip to content

Module Containers

You will learn the catalogue of ready-made containers — PostgreSQL, MySQL, Kafka, LocalStack, Elasticsearch — and how each exposes typed accessors like getJdbcUrl() and getBootstrapServers() so tests wire to the real thing without hardcoded ports. Interviewers ask about these because 'test your repository against real Postgres, not H2' and 'test your consumer against real Kafka' are the canonical Testcontainers scenarios.

on this pageshow

questions

5

In Testcontainers, what does PostgreSQLContainer give you that a plain GenericContainer does not?

level: juniorimportance: must knowfreq 66%

answer

  1. A subclass, not a different mechanism
  2. Image environment variables already wired
  3. Readiness means a real connection, not a port
  4. Accessors already include the random port
  5. The driver is a separate dependency

basics

~20 s

A module container ships the right image defaults, the environment wiring the image expects, a database-aware readiness check, and typed accessors — getJdbcUrl, getUsername, getPassword, getDatabaseName — that already contain the randomly mapped host port.

solid answer

~40 s

`PostgreSQLContainer` is a subclass of `GenericContainer` specialised for the official Postgres image. It sets the credential and database-name environment variables that image reads, declares the right port, and — most valuable — waits until a JDBC connection actually succeeds before `start()` returns, rather than merely until a port accepts TCP. It then exposes typed accessors: `getJdbcUrl()` returns a ready URL including the ephemeral host port, alongside `getUsername()`, `getPassword()`, `getDatabaseName()` and `getDriverClassName()`. Configuration goes through `withDatabaseName`, `withUsername`, `withPassword` and `withInitScript`. Practically: you add the module artifact plus the JDBC driver to the test classpath, and your test wires a DataSource from those accessors instead of reimplementing the environment variables and readiness logic yourself.

code

java · 12 lines
java
@Container
static final PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>(DockerImageName.parse("postgres:16-alpine"))
                .withDatabaseName("orders")
                .withUsername("app")
                .withPassword("secret")
                .withInitScript("db/baseline.sql");

HikariConfig config = new HikariConfig();
config.setJdbcUrl(postgres.getJdbcUrl());      // already contains the mapped host port
config.setUsername(postgres.getUsername());
config.setPassword(postgres.getPassword());

go deeper

for a junior

Be able to add the module dependency, start the container, and build a DataSource from its URL, user and password accessors instead of hardcoding anything.

for a middle

Explain what the module adds over the base class — environment wiring, a connection-based readiness check, typed accessors — and why the URL cannot be known before start.

for a senior

Argue the fidelity case for a real engine over an in-memory substitute, and know when writing your own container beats waiting for a module that does not exist.

for a principal

Decide which technologies the organisation tests against for real versus stubs, and standardise module and BOM versions so every suite behaves the same way on every agent.

## Modules are subclasses that encode expertise Testcontainers ships a catalogue of *modules* — separate artifacts, each a subclass of `GenericContainer` tuned to a specific technology: PostgreSQL, MySQL, MariaDB, Kafka, Redpanda, LocalStack, MongoDB, Elasticsearch and many more. Nothing in a module is magic; every part of it could be written by hand on a raw `GenericContainer`. The point is that the parts you would get subtly wrong are already right. ## What PostgreSQLContainer supplies **A sane default image and version handling.** You construct it with an explicit image reference (`postgres:16-alpine`, for instance) and the module knows the image's conventions. **Environment wiring.** The official Postgres image is configured through environment variables for the database name, user and password. The module sets them from `withDatabaseName`, `withUsername` and `withPassword`, with defaults so a zero-configuration container still works. **A readiness check that means something.** This is the biggest win and the one candidates undersell. A generic port check tells you the daemon bound the port; it does not tell you the server has finished initialising the cluster and will accept authenticated queries. Postgres in particular starts, restarts internally during first-time initialisation, and is briefly reachable while still refusing connections. The JDBC module waits until it can genuinely open a connection, which removes an entire family of "connection refused on the very first test" flakes. **Typed accessors.** `getJdbcUrl()` composes host, ephemeral mapped port and database name into a URL you can hand straight to a DataSource. `getUsername()`, `getPassword()`, `getDatabaseName()` and `getDriverClassName()` complete the set. Because the host port is chosen at runtime, these must be read *after* the container has started — reading a URL before start has no port to include. **Initialisation hooks.** `withInitScript("…")` runs a SQL script from the classpath once the database is ready, which is convenient for a fixed baseline schema in tests that are not exercising your migration tool. ## Dependencies Two things must be on the test classpath: the module artifact (for PostgreSQL, `org.testcontainers:postgresql`) and the actual JDBC driver for the database. The module does not bundle a driver — it produces a URL, and something has to be able to open it. Teams typically import `org.testcontainers:testcontainers-bom` as a platform so every Testcontainers artifact shares one version. ## The same shape across the catalogue Once you have seen one module the rest are predictable, and interviewers like hearing you generalise: `MySQLContainer` mirrors the Postgres one, `MongoDBContainer` exposes a connection string for a single-node replica set (so transactions work in tests), `ElasticsearchContainer` exposes an HTTP host address, and the Kafka modules expose bootstrap servers. The pattern is always: correct defaults, a service-appropriate readiness check, and typed accessors for whatever coordinates a client of that technology needs. ## When you still fall back to GenericContainer For an in-house service, a niche broker, or any image with no module, you wrap it in `GenericContainer` and supply the environment, ports and readiness check yourself. That is entirely legitimate — just understand you are hand-rolling exactly what a module would have given you, and that the readiness check is the part most likely to bite. ## Why teams adopt this at all The reason a real Postgres in a container beats an in-memory substitute is fidelity: your repository code, your dialect, your migrations and your SQL run against the engine that production runs. The reason it is *practical* is the module — without typed accessors and a trustworthy readiness check, every team would reimplement the same brittle setup, and most would get the readiness part wrong. ## Interview framing A good junior answer names the accessors and says the URL already contains the random port. A better one adds the readiness check and explains why a port check would be insufficient for a database. The trap to avoid is the assumption that a module means "a smaller API" — it is the same container API with domain knowledge baked in.

  • Why is a database module's readiness check stronger than a plain port check?
    A port becomes reachable as soon as something binds it, but a database on first boot initialises its cluster and may restart internally before it will accept authenticated queries. The JDBC modules wait until a real connection succeeds, which removes the classic 'connection refused on the very first test' flake.
  • How do other Testcontainers modules follow the same shape?
    Each exposes the coordinates a client of that technology needs: MySQL mirrors PostgreSQL's JDBC accessors, MongoDB exposes a connection string for a single-node replica set so transactions work, Elasticsearch exposes an HTTP host address, and the Kafka modules expose bootstrap servers. Correct defaults, a service-appropriate readiness check, typed accessors.
  • When would you still wrap a database image in GenericContainer yourself?
    When no module exists — an in-house image, a vendor build, or a niche engine. It works fine, but you take on the environment wiring and, critically, the readiness check. That last part is where hand-rolled containers usually go wrong, so budget for it rather than settling for a port check.

saying these in an interview costs you the question

  • Hardcodes localhost:5432 instead of the URL accessor
  • Reads the JDBC URL before starting the container
  • Assumes the module bundles the JDBC driver
  • Thinks a port check is enough for a database
  • Believes a module is required to use any image

context

open as a page

In a Testcontainers test, why must Kafka clients use KafkaContainer.getBootstrapServers()?

level: middleimportance: should knowfreq 42%

basics

~20 s

The broker's port is published on a random host port, and a Kafka client reconnects using the address the broker advertises. The module configures those advertised listeners to match the mapped host port and returns the usable address from getBootstrapServers().

open as a page

How do you point an AWS SDK v2 client at Testcontainers' LocalStackContainer?

level: middleimportance: should knowfreq 26%

basics

~10 s

Override the client's endpoint with the container's getEndpoint() URI, supply static credentials built from getAccessKey() and getSecretKey(), and set the region from getRegion(). For S3, also enable path-style addressing.

open as a page

In Testcontainers, what happens when a test connects to a jdbc:tc:postgresql:// URL?

level: middleimportance: should knowfreq 38%

basics

~20 s

Testcontainers registers a JDBC driver, ContainerDatabaseDriver, that intercepts the tc: scheme. On the first connection it starts the matching database container, proxies the connection to it, and stops the container when the last connection closes.

open as a page

Your repository tests run on H2 — what bugs slip through that a PostgreSQLContainer would catch?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Everything that depends on the real engine: PostgreSQL-only SQL and types, migration scripts that only ever ran against H2, identifier casing and quoting rules, constraint and error semantics, and concurrency behaviour such as locking and isolation. H2 tests prove your Java, not your database.

open as a page