The PostgreSQLContainer class provides full lifecycle management for PostgreSQL database containers in Java integration tests. It automatically handles container startup, port mapping, connection management, and cleanup.
Main container class for managing PostgreSQL instances in Docker containers.
/**
* Testcontainers implementation for PostgreSQL.
* Supported images: postgres, pgvector/pgvector
* Exposed ports: 5432
*
* @param <SELF> Self-referential generic type for fluent API
*/
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;
/**
* Create a PostgreSQL container with the default image (postgres:9.6.12)
* @deprecated Use PostgreSQLContainer(String) or PostgreSQLContainer(DockerImageName) instead
*/
@Deprecated
public PostgreSQLContainer();
/**
* Create a PostgreSQL container with a specific Docker image name
* @param dockerImageName Full Docker image name (e.g., "postgres:15")
* @throws IllegalArgumentException if dockerImageName is null or empty
*/
public PostgreSQLContainer(String dockerImageName);
/**
* Create a PostgreSQL container with a DockerImageName object
* @param dockerImageName DockerImageName instance
* @throws IllegalArgumentException if dockerImageName is null
*/
public PostgreSQLContainer(DockerImageName dockerImageName);
}Usage Examples:
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
// Using default image (deprecated)
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>();
// Using specific version
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
// Using DockerImageName for better validation
DockerImageName imageName = DockerImageName.parse("postgres:15");
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(imageName);
// Using pgvector variant
PostgreSQLContainer<?> pgvector = new PostgreSQLContainer<>("pgvector/pgvector:pg16");
// Using PostGIS image with compatibility check
DockerImageName postgisImage = DockerImageName
.parse("postgis/postgis:16-3.4")
.asCompatibleSubstituteFor("postgres");
PostgreSQLContainer<?> postgis = new PostgreSQLContainer<>(postgisImage);Error Handling for Image Names:
// Validate image name before use
try {
DockerImageName imageName = DockerImageName.parse("postgres:15")
.asCompatibleSubstituteFor("postgres");
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(imageName);
} catch (IllegalArgumentException e) {
// Handle invalid image name
System.err.println("Invalid Docker image: " + e.getMessage());
throw e;
}
// Handle null image name
try {
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>((String) null);
} catch (IllegalArgumentException e) {
// Image name cannot be null
}
// Handle empty image name
try {
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("");
} catch (IllegalArgumentException e) {
// Image name cannot be empty
}Methods for retrieving database connection details after the container has started. These methods are only valid after start() has been called.
/**
* Get the PostgreSQL JDBC driver class name
* @return "org.postgresql.Driver"
* @throws IllegalStateException if container is not started
*/
public String getDriverClassName();
/**
* Get the JDBC connection URL for the running PostgreSQL container
* Includes host, mapped port, database name, and any URL parameters
* @return JDBC URL in format: jdbc:postgresql://host:port/database?params
* @throws IllegalStateException if container is not started
*/
public String getJdbcUrl();
/**
* Get the configured database name
* @return Database name (default: "test")
*/
public String getDatabaseName();
/**
* Get the configured username
* @return Username (default: "test")
*/
public String getUsername();
/**
* Get the configured password
* @return Password (default: "test")
*/
public String getPassword();
/**
* Get the test query string used to verify database connectivity
* @return "SELECT 1"
*/
public String getTestQueryString();Usage Examples:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Get connection details
String jdbcUrl = postgres.getJdbcUrl();
// Example: "jdbc:postgresql://localhost:54321/test?loggerLevel=OFF"
String username = postgres.getUsername(); // "test"
String password = postgres.getPassword(); // "test"
String database = postgres.getDatabaseName(); // "test"
String driver = postgres.getDriverClassName(); // "org.postgresql.Driver"
String testQuery = postgres.getTestQueryString(); // "SELECT 1"
// Use with JDBC
try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) {
// Execute test query
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(testQuery)) {
rs.next();
int result = rs.getInt(1); // result is 1
}
}
}Validating Connection Information:
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Verify connection details are available
assert postgres.getJdbcUrl() != null && !postgres.getJdbcUrl().isEmpty();
assert postgres.getUsername() != null && !postgres.getUsername().isEmpty();
assert postgres.getPassword() != null && !postgres.getPassword().isEmpty();
assert postgres.getDatabaseName() != null && !postgres.getDatabaseName().isEmpty();
assert postgres.getDriverClassName().equals("org.postgresql.Driver");
assert postgres.getTestQueryString().equals("SELECT 1");
// Test connection
try (Connection conn = DriverManager.getConnection(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword())) {
assert conn.isValid(5); // Verify connection is valid
// Verify database name
assert conn.getCatalog().equals(postgres.getDatabaseName());
}
}
// Error: Calling getJdbcUrl() before start()
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
try {
String url = postgres.getJdbcUrl(); // Throws IllegalStateException
} catch (IllegalStateException e) {
// Container must be started first
}Fluent API methods for configuring the PostgreSQL container before starting it. All methods return SELF for method chaining.
/**
* Configure the database name to create on container startup
* @param databaseName Database name (must not be null)
* @return this for method chaining
* @throws IllegalArgumentException if databaseName is null
*/
public SELF withDatabaseName(String databaseName);
/**
* Configure the username for database access
* @param username Username (must not be null)
* @return this for method chaining
* @throws IllegalArgumentException if username is null
*/
public SELF withUsername(String username);
/**
* Configure the password for database access
* @param password Password (must not be null)
* @return this for method chaining
* @throws IllegalArgumentException if password is null
*/
public SELF withPassword(String password);Usage Examples:
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("myapp")
.withUsername("appuser")
.withPassword("secret123");
postgres.start();
String jdbcUrl = postgres.getJdbcUrl();
// "jdbc:postgresql://localhost:54321/myapp?loggerLevel=OFF"
// Verify configuration
assert postgres.getDatabaseName().equals("myapp");
assert postgres.getUsername().equals("appuser");
assert postgres.getPassword().equals("secret123");Configuration Validation:
// Validate configuration before starting
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("myapp")
.withUsername("appuser")
.withPassword("secret123");
// Verify configuration is set
assert "myapp".equals(postgres.getDatabaseName());
assert "appuser".equals(postgres.getUsername());
assert "secret123".equals(postgres.getPassword());
postgres.start();
// Verify configuration was applied
assert "myapp".equals(postgres.getDatabaseName());
assert "myapp".equals(postgres.getDatabaseName()); // Still accessible after start
// Error: null database name
try {
postgres.withDatabaseName(null);
} catch (IllegalArgumentException e) {
// Database name cannot be null
}Methods inherited from JdbcDatabaseContainer and GenericContainer for managing container lifecycle.
/**
* Start the PostgreSQL container
* Pulls the Docker image if needed, creates and starts the container,
* waits for PostgreSQL to be ready, and sets up port mappings
* @throws ContainerLaunchException if container fails to start
* @throws TimeoutException if container startup exceeds timeout
*/
public void start();
/**
* Stop the PostgreSQL container
* Stops and removes the container, freeing resources
*/
public void stop();
/**
* Check if the container is currently running
* @return true if container is running, false otherwise
*/
public boolean isRunning();
/**
* Check if the container has been created
* @return true if container has been created, false otherwise
*/
public boolean isCreated();
/**
* Close the container (same as stop())
* Implements AutoCloseable for try-with-resources
*/
public void close();Usage Examples:
// Manual lifecycle management
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
postgres.start();
try {
// Use the database
String jdbcUrl = postgres.getJdbcUrl();
assert postgres.isRunning();
assert postgres.isCreated();
// ...
} finally {
postgres.stop();
assert !postgres.isRunning();
}
// Automatic lifecycle with try-with-resources (recommended)
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Container automatically stopped when exiting try block
}
// Error: Starting already started container
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
postgres.start();
try {
postgres.start(); // May throw IllegalStateException or be a no-op
} catch (IllegalStateException e) {
// Container already started
}Error Handling During Startup:
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
try {
postgres.start();
} catch (ContainerLaunchException e) {
// Container failed to start
System.err.println("Failed to start container: " + e.getMessage());
System.err.println("Container logs: " + postgres.getLogs());
// Check common issues
if (e.getMessage().contains("timeout")) {
System.err.println("Consider increasing startup timeout");
} else if (e.getMessage().contains("image")) {
System.err.println("Check Docker image availability");
} else if (e.getMessage().contains("port")) {
System.err.println("Port conflict detected");
}
throw e;
} catch (TimeoutException e) {
// Startup timeout exceeded
System.err.println("Container startup timeout: " + e.getMessage());
System.err.println("Consider increasing withStartupTimeoutSeconds()");
throw e;
} catch (Exception e) {
// Other errors
System.err.println("Unexpected error: " + e.getMessage());
throw e;
}Verifying Container State:
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
// Before start
assert !postgres.isRunning();
assert !postgres.isCreated();
postgres.start();
// After start
assert postgres.isRunning() : "Container should be running";
assert postgres.isCreated() : "Container should be created";
// Verify container is healthy
try (Connection conn = DriverManager.getConnection(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword())) {
assert conn.isValid(5) : "Connection should be valid";
}
postgres.stop();
// After stop
assert !postgres.isRunning();
}Methods for accessing container port and network information.
/**
* Get the list of ports exposed by this container
* @return List containing POSTGRESQL_PORT (5432)
* @throws IllegalStateException if container is not started
*/
public List<Integer> getExposedPorts();
/**
* Get the set of port numbers used for liveness checks
* @return Set containing the mapped PostgreSQL port
* @throws IllegalStateException if container is not started
*/
public Set<Integer> getLivenessCheckPortNumbers();
/**
* Get the host port mapped to the container's PostgreSQL port
* @param originalPort Container port (typically POSTGRESQL_PORT)
* @return Host port number
* @throws IllegalStateException if container is not started
* @throws IllegalArgumentException if originalPort is not exposed
*/
public Integer getMappedPort(int originalPort);
/**
* Get the container host address
* @return Host address (typically "localhost" or Docker host IP)
* @throws IllegalStateException if container is not started
*/
public String getHost();
/**
* Get the container ID
* @return Docker container ID
* @throws IllegalStateException if container is not started
*/
public String getContainerId();
/**
* Get the container name
* @return Docker container name
* @throws IllegalStateException if container is not started
*/
public String getContainerName();Usage Examples:
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Get exposed ports
List<Integer> exposedPorts = postgres.getExposedPorts();
// [5432]
assert exposedPorts.contains(PostgreSQLContainer.POSTGRESQL_PORT);
// Get mapped port
Integer mappedPort = postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT);
// e.g., 54321 (dynamically assigned)
assert mappedPort != null;
assert mappedPort > 1024 && mappedPort < 65536;
// Get host
String host = postgres.getHost();
// "localhost" or Docker host IP
assert host != null && !host.isEmpty();
// Get container ID
String containerId = postgres.getContainerId();
assert containerId != null && !containerId.isEmpty();
// Get container name
String containerName = postgres.getContainerName();
assert containerName != null && !containerName.isEmpty();
// Build connection URL manually
String url = String.format(
"jdbc:postgresql://%s:%d/%s",
host,
mappedPort,
postgres.getDatabaseName()
);
assert url.equals(postgres.getJdbcUrl());
}Port Validation:
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Verify port mapping
Integer mappedPort = postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT);
assert mappedPort != null : "Port should be mapped";
assert mappedPort > 1024 : "Port should be above privileged range";
assert mappedPort < 65536 : "Port should be valid";
// Verify port is accessible
try (Socket socket = new Socket(postgres.getHost(), mappedPort)) {
assert socket.isConnected() : "Port should be accessible";
}
// Error: Requesting unmapped port
try {
postgres.getMappedPort(8080); // Port 8080 is not exposed
} catch (IllegalArgumentException e) {
// Port not exposed
}
}Methods for accessing container logs, executing commands, and getting container metadata.
/**
* Get all container logs as a string
* @return Container logs
* @throws IllegalStateException if container is not started
*/
public String getLogs();
/**
* Get container logs filtered by output type
* @param outputType Output type filter (STDOUT, STDERR, or END)
* @return Filtered container logs
* @throws IllegalStateException if container is not started
*/
public String getLogs(OutputFrame.OutputType outputType);
/**
* Execute a command in the running container
* @param command Command and arguments to execute
* @return Execution result with exit code, stdout, and stderr
* @throws IllegalStateException if container is not started
* @throws UnsupportedOperationException if container doesn't support exec
*/
public ExecResult execInContainer(String... command);
/**
* Execute a command in the running container as a specific user
* @param user User to execute command as
* @param command Command and arguments to execute
* @return Execution result with exit code, stdout, and stderr
* @throws IllegalStateException if container is not started
*/
public ExecResult execInContainer(String user, String... command);
/**
* Execute a command in the running container with timeout
* @param timeout Maximum time to wait for command completion
* @param command Command and arguments to execute
* @return Execution result with exit code, stdout, and stderr
* @throws IllegalStateException if container is not started
* @throws TimeoutException if command execution exceeds timeout
*/
public ExecResult execInContainer(Duration timeout, String... command);
/**
* Get the Docker image name used by this container
* @return Docker image name
*/
public String getDockerImageName();Usage Examples:
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Get all logs
String logs = postgres.getLogs();
assert logs != null;
assert logs.contains("database system is ready");
// Get filtered logs
String stdout = postgres.getLogs(OutputFrame.OutputType.STDOUT);
String stderr = postgres.getLogs(OutputFrame.OutputType.STDERR);
// Execute command in container
ExecResult result = postgres.execInContainer("psql", "-U", "test", "-d", "test", "-c", "SELECT version()");
assert result.getExitCode() == 0;
assert result.getStdout().contains("PostgreSQL");
// Execute as specific user
ExecResult result2 = postgres.execInContainer("postgres", "psql", "-c", "SELECT 1");
assert result2.getExitCode() == 0;
// Execute with timeout
ExecResult result3 = postgres.execInContainer(
Duration.ofSeconds(10),
"psql", "-U", "test", "-d", "test", "-c", "SELECT pg_sleep(5)"
);
assert result3.getExitCode() == 0;
// Get Docker image name
String imageName = postgres.getDockerImageName();
assert imageName.equals("postgres:15");
}When not explicitly configured, the PostgreSQL container uses these defaults:
The container starts PostgreSQL with the following command:
postgres -c fsync=offThe fsync=off option disables filesystem synchronization for better performance in test environments. This is safe for testing but should never be used in production.
Customizing the command:
// Override default command
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withCommand("postgres -c fsync=off -c synchronous_commit=off");
// Reset to default
PostgreSQLContainer<?> postgres2 = new PostgreSQLContainer<>("postgres:15")
.withCommand("postgres -c max_connections=200")
.withCommand(); // Reset to defaultThe container waits for PostgreSQL to be ready by monitoring log output for the message:
database system is ready to accept connectionsThis message must appear twice (startup and recovery completion), with a timeout of 60 seconds.
Custom Wait Strategy:
import org.testcontainers.containers.wait.strategy.Wait;
import java.time.Duration;
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.waitingFor(Wait.forLogMessage(".*database system is ready to accept connections.*", 2)
.withStartupTimeout(Duration.ofSeconds(120)));
// Custom wait with health check
PostgreSQLContainer<?> postgres2 = new PostgreSQLContainer<>("postgres:15")
.waitingFor(Wait.forHealthcheck()
.withStartupTimeout(Duration.ofSeconds(180)));The container automatically:
POSTGRES_DB environment variable to the configured database namePOSTGRES_USER environment variable to the configured usernamePOSTGRES_PASSWORD environment variable to the configured passwordloggerLevel=OFF to JDBC URL to reduce PostgreSQL driver logging noisestart() returnsimport org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class DatabaseTest {
public void testDatabaseOperations() throws Exception {
// Create container with custom configuration
DockerImageName image = DockerImageName.parse("postgres:15");
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(image)
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass")) {
// Start container
postgres.start();
// Get connection details
String jdbcUrl = postgres.getJdbcUrl();
String username = postgres.getUsername();
String password = postgres.getPassword();
// Connect to database
try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) {
// Create table
try (Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(100))");
stmt.execute("INSERT INTO users (name) VALUES ('Alice'), ('Bob')");
}
// Query data
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM users")) {
rs.next();
int count = rs.getInt(1);
// count is 2
}
}
} // Container automatically stopped and removed
}
}Always use try-with-resources to ensure containers are properly cleaned up:
// Correct: Automatic cleanup
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Use container
} // Container automatically stopped
// Incorrect: May leak resources
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
postgres.start();
// If exception occurs before stop(), container may not be cleaned up
postgres.stop();try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Check container state
if (!postgres.isRunning()) {
throw new IllegalStateException("Container is not running");
}
// Verify container is ready
try (Connection conn = DriverManager.getConnection(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword())) {
// Container is ready
}
}PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
try {
postgres.start();
} catch (Exception e) {
// Get diagnostic information
System.err.println("Error: " + e.getMessage());
System.err.println("Container ID: " + postgres.getContainerId());
System.err.println("Container logs:");
System.err.println(postgres.getLogs());
// Check Docker availability
// Check disk space
// Check port availability
}try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Verify connection details
String jdbcUrl = postgres.getJdbcUrl();
System.out.println("JDBC URL: " + jdbcUrl);
System.out.println("Host: " + postgres.getHost());
System.out.println("Port: " + postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT));
// Test connection with retry
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
try (Connection conn = DriverManager.getConnection(
jdbcUrl,
postgres.getUsername(),
postgres.getPassword())) {
// Connection successful
break;
} catch (SQLException e) {
if (i == maxRetries - 1) {
System.err.println("Connection failed after " + maxRetries + " attempts");
System.err.println("Container logs: " + postgres.getLogs());
throw e;
}
Thread.sleep(1000); // Wait before retry
}
}
}try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
// Get container information
System.out.println("Container ID: " + postgres.getContainerId());
System.out.println("Container name: " + postgres.getContainerName());
System.out.println("Docker image: " + postgres.getDockerImageName());
System.out.println("Container logs (last 100 lines):");
System.out.println(postgres.getLogs());
// Get network information
System.out.println("Host: " + postgres.getHost());
System.out.println("Mapped port: " + postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT));
// Execute diagnostic commands
ExecResult version = postgres.execInContainer("psql", "-U", "test", "-d", "test", "-c", "SELECT version()");
System.out.println("PostgreSQL version: " + version.getStdout());
}