Testcontainers PostgreSQL provides lightweight, throwable PostgreSQL database container instances for Java integration testing. It extends JdbcDatabaseContainer to offer seamless integration with PostgreSQL database instances running in Docker containers, automatically managing the container lifecycle and providing JDBC connection details.
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.21.4</version>
<scope>test</scope>
</dependency>import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;For R2DBC support:
import org.testcontainers.containers.PostgreSQLR2DBCDatabaseContainer;
import io.r2dbc.spi.ConnectionFactoryOptions;import org.testcontainers.containers.PostgreSQLContainer;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class PostgreSQLTest {
public void testWithPostgreSQL() throws Exception {
// Create and start a PostgreSQL container
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Get connection details
String jdbcUrl = postgres.getJdbcUrl();
String username = postgres.getUsername();
String password = postgres.getPassword();
// Connect and execute queries
try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password);
Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT 1");
rs.next();
int result = rs.getInt(1);
// result is 1
}
} // Container automatically stopped
}
}The Testcontainers PostgreSQL module is built around several key components:
Core PostgreSQL container class with full lifecycle management, configuration, and JDBC connectivity.
public class PostgreSQLContainer<SELF extends PostgreSQLContainer<SELF>>
extends JdbcDatabaseContainer<SELF> {
// Constants
public static final String NAME = "postgresql";
public static final String IMAGE = "postgres";
public static final String DEFAULT_TAG = "9.6.12";
public static final Integer POSTGRESQL_PORT = 5432;
// Constructors
@Deprecated
public PostgreSQLContainer();
public PostgreSQLContainer(String dockerImageName);
public PostgreSQLContainer(DockerImageName dockerImageName);
// Connection information (PostgreSQLContainer-specific)
public String getDriverClassName();
public String getJdbcUrl();
public String getDatabaseName();
public String getUsername();
public String getPassword();
public String getTestQueryString();
// Configuration methods (PostgreSQLContainer-specific)
public SELF withDatabaseName(String databaseName);
public SELF withUsername(String username);
public SELF withPassword(String password);
}Inherited Methods from JdbcDatabaseContainer:
// Lifecycle management
public void start();
public void stop();
public boolean isRunning();
public boolean isCreated();
// Port and network information
public String getHost();
public Integer getMappedPort(int originalPort);
public List<Integer> getExposedPorts();
public Set<Integer> getLivenessCheckPortNumbers();
public String getContainerId();
public String getContainerName();
// Configuration (inherited from JdbcDatabaseContainer)
public SELF withInitScript(String initScriptPath);
public SELF withInitScripts(String... initScriptPaths);
public SELF withInitScripts(Iterable<String> initScriptPaths);
public SELF withCommand(String cmd);
public SELF withCommand(String... commandParts);
public SELF withCommand();
public SELF withUrlParam(String paramName, String paramValue);
public SELF withEnv(String key, String value);
public SELF withEnv(Map<String, String> env);
public SELF withStartupTimeoutSeconds(int startupTimeoutSeconds);
public SELF withConnectTimeoutSeconds(int connectTimeoutSeconds);
public SELF withReuse(boolean reusable);
public SELF withExposedPorts(Integer... ports);
public SELF withNetworkMode(String networkMode);
public SELF withNetworkAliases(String... aliases);
// Container information
public String getDockerImageName();
public String getLogs();
public String getLogs(OutputFrame.OutputType outputType);
public ExecResult execInContainer(String... command);
public ExecResult execInContainer(String user, String... command);
public ExecResult execInContainer(Duration timeout, String... command);
// Wait strategies
public SELF waitingFor(WaitStrategy waitStrategy);
public SELF withStartupCheckStrategy(StartupCheckStrategy strategy);
// Resource management
public void close();Supported Docker Images:
postgres - Official PostgreSQL images (all versions)pgvector/pgvector - PostgreSQL with vector similarity search extensionDefault Configuration:
Complete PostgreSQL Container Documentation
Fluent API for configuring PostgreSQL containers with custom settings, environment variables, initialization scripts, and startup parameters.
// PostgreSQLContainer-specific configuration
public SELF withDatabaseName(String databaseName);
public SELF withUsername(String username);
public SELF withPassword(String password);
// Inherited configuration methods (from JdbcDatabaseContainer)
public SELF withInitScript(String initScriptPath);
public SELF withInitScripts(String... initScriptPaths);
public SELF withInitScripts(Iterable<String> initScriptPaths);
public SELF withCommand(String cmd);
public SELF withCommand(String... commandParts);
public SELF withCommand();
public SELF withUrlParam(String paramName, String paramValue);
public SELF withEnv(String key, String value);
public SELF withEnv(Map<String, String> env);
public SELF withStartupTimeoutSeconds(int startupTimeoutSeconds);
public SELF withConnectTimeoutSeconds(int connectTimeoutSeconds);
public SELF withReuse(boolean reusable);
public SELF withExposedPorts(Integer... ports);
public SELF withNetworkMode(String networkMode);
public SELF withNetworkAliases(String... aliases);
public SELF waitingFor(WaitStrategy waitStrategy);
public SELF withStartupCheckStrategy(StartupCheckStrategy strategy);Complete Configuration Documentation
Special JDBC URL scheme that automatically manages container lifecycle without explicit start/stop calls.
// JDBC URL Format
// jdbc:tc:postgresql:<version>://<host>/<database>?user=<user>&password=<password>
public class PostgreSQLContainerProvider extends JdbcDatabaseContainerProvider {
public static final String USER_PARAM = "user";
public static final String PASSWORD_PARAM = "password";
public boolean supports(String databaseType);
public JdbcDatabaseContainer newInstance();
public JdbcDatabaseContainer newInstance(String tag);
public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl);
}JDBC URL Format:
jdbc:tc:postgresql:<version>://<host>/<database>?<parameters>Supported Parameters:
user - Database username (default: "test")password - Database password (default: "test")TC_INITSCRIPT - Path to initialization script (classpath resource)TC_INITFUNCTION - Init function class nameTC_DAEMON - Keep container running (true/false)TC_TMPFS - Tmpfs mount configurationJDBC URL Pattern Documentation
Reactive database connectivity support for non-blocking PostgreSQL access using R2DBC.
public class PostgreSQLR2DBCDatabaseContainer implements R2DBCDatabaseContainer {
public PostgreSQLR2DBCDatabaseContainer(PostgreSQLContainer<?> container);
public static ConnectionFactoryOptions getOptions(PostgreSQLContainer<?> container);
public ConnectionFactoryOptions configure(ConnectionFactoryOptions options);
public void start();
public void stop();
}
public class PostgreSQLR2DBCDatabaseContainerProvider
implements R2DBCDatabaseContainerProvider {
public boolean supports(ConnectionFactoryOptions options);
public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options);
public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options);
}R2DBC URL Format:
r2dbc:tc:postgresql:///<database>?TC_IMAGE_TAG=<version>Support for specialized PostgreSQL distributions including PostGIS, TimescaleDB, and PgVector.
// PostGIS Provider
public class PostgisContainerProvider extends JdbcDatabaseContainerProvider {
public static final String USER_PARAM = "user";
public static final String PASSWORD_PARAM = "password";
public boolean supports(String databaseType); // Returns true for "postgis"
public JdbcDatabaseContainer newInstance(); // Default: postgis/postgis:12-3.0
public JdbcDatabaseContainer newInstance(String tag);
public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl);
}
// TimescaleDB Provider
public class TimescaleDBContainerProvider extends JdbcDatabaseContainerProvider {
public static final String USER_PARAM = "user";
public static final String PASSWORD_PARAM = "password";
public boolean supports(String databaseType); // Returns true for "timescaledb"
public JdbcDatabaseContainer newInstance(); // Default: timescale/timescaledb:2.1.0-pg11
public JdbcDatabaseContainer newInstance(String tag);
public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl);
}
// PgVector Provider
public class PgVectorContainerProvider extends JdbcDatabaseContainerProvider {
public static final String USER_PARAM = "user";
public static final String PASSWORD_PARAM = "password";
public boolean supports(String databaseType); // Returns true for "pgvector"
public JdbcDatabaseContainer newInstance(); // Default: pgvector/pgvector:pg16
public JdbcDatabaseContainer newInstance(String tag);
public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl);
}JDBC URL Formats:
jdbc:tc:postgis:<version>://<host>/<database>?user=<user>&password=<password>
jdbc:tc:timescaledb:<version>://<host>/<database>?user=<user>&password=<password>
jdbc:tc:pgvector://<host>/<database>?user=<user>&password=<password>PostgreSQL Variants Documentation
// DockerImageName - Used for specifying and validating Docker images
public class DockerImageName {
public static DockerImageName parse(String fullImageName);
public DockerImageName withTag(String tag);
public DockerImageName asCompatibleSubstituteFor(String... baseImageNames);
}Container startup timeout:
// Increase startup timeout for slow environments
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withStartupTimeoutSeconds(180);Connection failures:
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Wait for container to be ready
if (!postgres.isRunning()) {
throw new IllegalStateException("Container failed to start");
}
// Verify connection
try (Connection conn = DriverManager.getConnection(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword())) {
// Connection successful
} catch (SQLException e) {
// Handle connection error
System.err.println("Connection failed: " + e.getMessage());
System.err.println("Container logs: " + postgres.getLogs());
throw e;
}
}Docker image pull failures:
// Use DockerImageName for better error messages
DockerImageName image = DockerImageName.parse("postgres:15")
.asCompatibleSubstituteFor("postgres");
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(image);Port conflicts:
// Check if port is already in use
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
try {
postgres.start();
} catch (Exception e) {
if (e.getMessage().contains("port") || e.getMessage().contains("bind")) {
System.err.println("Port conflict detected. Container will use random port.");
// Retry - container will automatically use different port
}
throw e;
}Always use try-with-resources to ensure proper cleanup:
// Correct: Automatic cleanup
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Use container
} // Container automatically stopped and removed
// Incorrect: Manual cleanup (may leak resources on exceptions)
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
postgres.start();
try {
// Use container
} finally {
postgres.stop(); // May not execute if exception occurs
}PostgreSQLContainer instances are not thread-safe. Each test should use its own container instance:
// Correct: One container per test
@Test
void test1() {
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Test code
}
}
@Test
void test2() {
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Test code
}
}
// Incorrect: Shared container across threads
private static PostgreSQLContainer<?> sharedContainer; // NOT thread-safeEnable container logging for troubleshooting:
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Get container logs
String logs = postgres.getLogs();
System.out.println("Container logs: " + logs);
// Get container info
System.out.println("Container ID: " + postgres.getContainerId());
System.out.println("Container name: " + postgres.getContainerName());
System.out.println("Host: " + postgres.getHost());
System.out.println("Port: " + postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT));
System.out.println("Docker image: " + postgres.getDockerImageName());
}For faster test execution in development, enable container reuse:
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withReuse(true);Note: Requires .testcontainers.properties file in home directory with:
testcontainers.reuse.enable=trueUse connection pooling for better performance:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
HikariConfig config = new HikariConfig();
config.setJdbcUrl(postgres.getJdbcUrl());
config.setUsername(postgres.getUsername());
config.setPassword(postgres.getPassword());
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
try (HikariDataSource dataSource = new HikariDataSource(config)) {
// Use pooled connections
}
}For faster test execution, use optimized PostgreSQL settings:
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withCommand("postgres -c fsync=off -c synchronous_commit=off -c full_page_writes=off");import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.containers.PostgreSQLContainer;
@Testcontainers
public class SharedContainerTest {
@Container
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withInitScript("schema.sql");
@Test
void test1() {
String jdbcUrl = postgres.getJdbcUrl();
// Use container
}
@Test
void test2() {
String jdbcUrl = postgres.getJdbcUrl();
// Use same container instance
}
}import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.containers.PostgreSQLContainer;
@Testcontainers
public class PerTestContainerTest {
@Container
private final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15");
@Test
void test1() {
String jdbcUrl = postgres.getJdbcUrl();
// Each test gets its own container
}
@Test
void test2() {
String jdbcUrl = postgres.getJdbcUrl();
// Different container instance
}
}import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@SpringBootTest
@Testcontainers
public class SpringBootTest {
@Container
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
postgres.start();
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.datasource.driver-class-name",
() -> "org.postgresql.Driver");
}
}import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
@SpringBootTest
public class SpringBootJdbcUrlTest {
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url",
() -> "jdbc:tc:postgresql:15:///testdb?TC_INITSCRIPT=schema.sql");
registry.add("spring.datasource.driver-class-name",
() -> "org.testcontainers.jdbc.ContainerDatabaseDriver");
}
}