The Testcontainers JDBC driver provides a special URL scheme that automatically manages container lifecycle without explicit start/stop calls. This is ideal for simple test scenarios where you want to connect to a PostgreSQL database using just a JDBC URL.
Factory class that creates PostgreSQL containers from JDBC URLs using the jdbc:tc: scheme. Registered via Java SPI.
/**
* Provider for creating PostgreSQL containers via JDBC URL patterns
* Registered via Java SPI (Service Provider Interface)
*/
public class PostgreSQLContainerProvider extends JdbcDatabaseContainerProvider {
// URL parameter names
public static final String USER_PARAM = "user";
public static final String PASSWORD_PARAM = "password";
/**
* Check if this provider supports the given database type
* @param databaseType Database type string (e.g., "postgresql")
* @return true if databaseType is "postgresql"
*/
public boolean supports(String databaseType);
/**
* Create a new PostgreSQL container instance with default configuration
* Uses postgres:9.6.12 as the default image
* @return New JdbcDatabaseContainer instance
*/
public JdbcDatabaseContainer newInstance();
/**
* Create a new PostgreSQL container instance with a specific version tag
* @param tag PostgreSQL version tag (e.g., "15", "14-alpine")
* @return New JdbcDatabaseContainer instance
* @throws IllegalArgumentException if tag is invalid
*/
public JdbcDatabaseContainer newInstance(String tag);
/**
* Create a new PostgreSQL container from a parsed JDBC connection URL
* Extracts database name, user, password, and other parameters from URL
* @param connectionUrl Parsed connection URL object
* @return New JdbcDatabaseContainer configured from URL
* @throws IllegalArgumentException if connectionUrl is invalid
*/
public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl);
}The special JDBC URL format for automatic container management:
jdbc:tc:postgresql:<version>://<host>/<database>?<parameters>URL Components:
jdbc:tc: - Testcontainers JDBC prefixpostgresql - Database type identifier<version> - Optional PostgreSQL version tag (e.g., 15, 14-alpine, 16.1, latest)<host> - Placeholder host (ignored, can be any value like localhost or empty)<database> - Database name to create<parameters> - Query string with configuration parametersURL Format Variations:
// With version
jdbc:tc:postgresql:15:///testdb
// With host placeholder
jdbc:tc:postgresql:15://localhost/testdb
// Without version (uses default)
jdbc:tc:postgresql:///testdb
// With all parameters
jdbc:tc:postgresql:15:///testdb?user=testuser&password=testpass&TC_INITSCRIPT=schema.sqlTC_INITSCRIPT
TC_INITSCRIPT=schema.sqlTC_INITFUNCTION
TC_INITFUNCTION=com.example.DatabaseInitTC_DAEMON
TC_DAEMON=trueTC_TMPFS
/path:rw or /path:ro)TC_TMPFS=/var/lib/postgresql/data:rwTC_IMAGE_TAG
TC_IMAGE_TAG=15-alpineTC_STARTUP_TIMEOUT
TC_STARTUP_TIMEOUT=180user
user=myuserpassword
password=mypassssl
ssl=truesslmode
sslmode=requireconnectTimeout
connectTimeout=10loginTimeout
loginTimeout=5socketTimeout
socketTimeout=30ApplicationName
ApplicationName=MyAppcharSet
charSet=UTF-8currentSchema
currentSchema=myschematcpKeepAlive
tcpKeepAlive=truereadOnly
readOnly=trueprepareThreshold
prepareThreshold=5reWriteBatchedInserts
reWriteBatchedInserts=trueimport java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcUrlTest {
public void testWithJdbcUrl() throws Exception {
// Container automatically started on first connection
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT version()")) {
rs.next();
String version = rs.getString(1);
// PostgreSQL 15.x version string
}
} // Container automatically stopped when connection closes
}
}// Specify custom username and password
String jdbcUrl = "jdbc:tc:postgresql:15:///mydb?user=myuser&password=mypass";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Use connection
}// Use specific PostgreSQL versions
String jdbcUrl1 = "jdbc:tc:postgresql:16:///testdb"; // PostgreSQL 16
String jdbcUrl2 = "jdbc:tc:postgresql:15.3:///testdb"; // PostgreSQL 15.3
String jdbcUrl3 = "jdbc:tc:postgresql:14-alpine:///testdb"; // PostgreSQL 14 Alpine variant
String jdbcUrl4 = "jdbc:tc:postgresql:latest:///testdb"; // Latest version
try (Connection conn = DriverManager.getConnection(jdbcUrl1)) {
// Container with PostgreSQL 16 started automatically
}// Execute SQL script on container startup
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb?TC_INITSCRIPT=schema.sql";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Database schema from schema.sql is already loaded
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
// Query tables created by init script
}
}Init script example (schema.sql):
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL
);
INSERT INTO users (username) VALUES ('alice'), ('bob');// Use tmpfs for PostgreSQL data directory (faster but data not persisted)
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb?TC_TMPFS=/var/lib/postgresql/data:rw";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Database runs in memory for better performance
}// Keep container running after connection closes (reuse across tests)
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb?TC_DAEMON=true";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// First test
}
// Container still running
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Second test reuses same container
}// Add standard PostgreSQL JDBC parameters
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb" +
"?user=myuser" +
"&password=mypass" +
"&ssl=true" +
"&sslmode=require" +
"&connectTimeout=10" +
"&ApplicationName=MyApp";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Connection with SSL and custom settings
}import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class CompleteJdbcUrlExample {
public void testCompleteConfiguration() throws Exception {
// Build comprehensive JDBC URL
String jdbcUrl = "jdbc:tc:postgresql:15.3:///myapp_test" +
"?user=testuser" +
"&password=testpass123" +
"&TC_INITSCRIPT=schema.sql" +
"&TC_TMPFS=/var/lib/postgresql/data:rw" +
"&ssl=false" +
"&ApplicationName=IntegrationTest" +
"&connectTimeout=30" +
"&reWriteBatchedInserts=true";
// Container starts automatically on first connection
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Create data
String insertSql = "INSERT INTO users (username, email) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
pstmt.setString(1, "alice");
pstmt.setString(2, "alice@example.com");
pstmt.executeUpdate();
}
// Query data
String selectSql = "SELECT username, email FROM users WHERE username = ?";
try (PreparedStatement pstmt = conn.prepareStatement(selectSql)) {
pstmt.setString(1, "alice");
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
String username = rs.getString("username");
String email = rs.getString("email");
// username: "alice", email: "alice@example.com"
}
}
}
} // Container stopped automatically
}
}import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import static org.junit.Assert.assertEquals;
public class PostgreSQLJUnit4Test {
private static final String JDBC_URL = "jdbc:tc:postgresql:15:///testdb";
@Test
public void testDatabaseQuery() throws Exception {
try (Connection conn = DriverManager.getConnection(JDBC_URL);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT 1 AS result")) {
rs.next();
int result = rs.getInt("result");
assertEquals(1, result);
}
}
@Test
public void testAnotherQuery() throws Exception {
// Each test gets a fresh container by default
// Use TC_DAEMON=true to reuse containers
try (Connection conn = DriverManager.getConnection(JDBC_URL)) {
// Test code
}
}
}import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PostgreSQLJUnit5Test {
private static final String JDBC_URL = "jdbc:tc:postgresql:15:///testdb?TC_INITSCRIPT=schema.sql";
@Test
void testDatabaseQuery() throws Exception {
try (Connection conn = DriverManager.getConnection(JDBC_URL);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM users")) {
rs.next();
int count = rs.getInt(1);
assertEquals(0, count);
}
}
}import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import javax.sql.DataSource;
import java.sql.Connection;
@SpringBootTest
public class SpringBootPostgreSQLTest {
@DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
// Configure Spring datasource to use Testcontainers JDBC URL
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");
}
@Autowired
private DataSource dataSource;
@Test
void testDataSourceConnection() throws Exception {
try (Connection conn = dataSource.getConnection()) {
// Connection from Spring-managed datasource
// Container managed automatically
}
}
}The PostgreSQLContainerProvider is automatically registered via Java SPI mechanism. The registration is defined in:
META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProviderThis allows the Testcontainers JDBC driver to automatically detect and use the provider when it encounters jdbc:tc:postgresql: URLs.
getMappedPort() or getHost()Use JDBC URL Pattern when:
Use PostgreSQLContainer class when:
Malformed URL:
// Incorrect URL format
String invalidUrl = "jdbc:tc:postgresql://testdb"; // Missing version or host
try (Connection conn = DriverManager.getConnection(invalidUrl)) {
// This may fail
} catch (SQLException e) {
System.err.println("Invalid JDBC URL format: " + e.getMessage());
// Use correct format: jdbc:tc:postgresql:<version>:///<database>
}Correct URL format:
// Correct format
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb";
// Or with host placeholder
String jdbcUrl2 = "jdbc:tc:postgresql:15://localhost/testdb";Handling startup timeouts:
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb?TC_STARTUP_TIMEOUT=180";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Connection successful
} catch (SQLException e) {
if (e.getMessage().contains("timeout") || e.getMessage().contains("startup")) {
System.err.println("Container startup timeout. Possible causes:");
System.err.println("1. Docker not running");
System.err.println("2. Insufficient resources");
System.err.println("3. Network issues");
System.err.println("Consider using explicit container with increased timeout");
}
throw e;
}Using explicit container for debugging:
// When JDBC URL pattern fails, use explicit container for better error messages
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withStartupTimeoutSeconds(180);
try {
postgres.start();
String jdbcUrl = postgres.getJdbcUrl();
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Use connection
}
} catch (Exception e) {
System.err.println("Container logs: " + postgres.getLogs());
throw e;
}Handling init script failures:
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb?TC_INITSCRIPT=schema.sql";
try (Connection conn = DriverManager.getConnection(jdbcUrl);
Statement stmt = conn.createStatement()) {
// Verify init script executed
try (ResultSet rs = stmt.executeQuery(
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public'")) {
rs.next();
int tableCount = rs.getInt(1);
if (tableCount == 0) {
throw new IllegalStateException("Init script may have failed - no tables found");
}
}
} catch (SQLException e) {
System.err.println("Init script error: " + e.getMessage());
// Check if schema.sql exists in classpath
throw e;
}Using connection pooling:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb";
HikariConfig config = new HikariConfig();
config.setJdbcUrl(jdbcUrl);
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(30000);
try (HikariDataSource dataSource = new HikariDataSource(config)) {
try (Connection conn = dataSource.getConnection()) {
// Use pooled connection
}
}Validating URL parameters:
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb" +
"?user=testuser" +
"&password=testpass" +
"&TC_INITSCRIPT=schema.sql";
// Verify URL is well-formed
try {
new java.net.URL(jdbcUrl.replace("jdbc:tc:", "http://"));
} catch (java.net.MalformedURLException e) {
throw new IllegalArgumentException("Invalid JDBC URL: " + e.getMessage());
}
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Connection successful
}Enabling debug logging:
// Enable Testcontainers debug logging
System.setProperty("testcontainers.reuse.enable", "false");
System.setProperty("testcontainers.docker.client.strategy", "org.testcontainers.dockerclient.UnixSocketClientProviderStrategy");
String jdbcUrl = "jdbc:tc:postgresql:15:///testdb";
try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
// Connection successful
} catch (SQLException e) {
System.err.println("JDBC URL: " + jdbcUrl);
System.err.println("Error: " + e.getMessage());
System.err.println("Error code: " + e.getErrorCode());
System.err.println("SQL state: " + e.getSQLState());
throw e;
}