or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdindex.mdjdbc-url-pattern.mdpostgresql-container.mdpostgresql-variants.mdr2dbc-support.md
tile.json

jdbc-url-pattern.mddocs/

JDBC URL Pattern (Automatic Lifecycle)

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.

Complete API Reference

PostgreSQLContainerProvider

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);
}

JDBC URL Format

The special JDBC URL format for automatic container management:

jdbc:tc:postgresql:<version>://<host>/<database>?<parameters>

URL Components:

  • jdbc:tc: - Testcontainers JDBC prefix
  • postgresql - 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 parameters

URL 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.sql

Complete Parameter Reference

Testcontainers-Specific Parameters

TC_INITSCRIPT

  • Description: Path to initialization script (classpath resource)
  • Type: String
  • Example: TC_INITSCRIPT=schema.sql
  • Usage: Executes SQL script after container starts

TC_INITFUNCTION

  • Description: Fully qualified class name of init function
  • Type: String
  • Example: TC_INITFUNCTION=com.example.DatabaseInit
  • Usage: Calls static method to initialize database

TC_DAEMON

  • Description: Keep container running after connection closes
  • Type: Boolean (true/false)
  • Example: TC_DAEMON=true
  • Usage: Reuse container across multiple connections

TC_TMPFS

  • Description: Tmpfs mount configuration for performance
  • Type: String (format: /path:rw or /path:ro)
  • Example: TC_TMPFS=/var/lib/postgresql/data:rw
  • Usage: Mount data directory in memory for faster I/O

TC_IMAGE_TAG

  • Description: Alternative way to specify image version
  • Type: String
  • Example: TC_IMAGE_TAG=15-alpine
  • Usage: Specify PostgreSQL version

TC_STARTUP_TIMEOUT

  • Description: Container startup timeout in seconds
  • Type: Integer
  • Example: TC_STARTUP_TIMEOUT=180
  • Usage: Increase timeout for slow environments

Database Credentials

user

  • Description: Database username
  • Type: String
  • Default: "test"
  • Example: user=myuser
  • Required: No

password

  • Description: Database password
  • Type: String
  • Default: "test"
  • Example: password=mypass
  • Required: No

PostgreSQL JDBC Parameters

ssl

  • Description: Enable SSL connection
  • Type: Boolean (true/false)
  • Default: false
  • Example: ssl=true

sslmode

  • Description: SSL connection mode
  • Type: String (disable/allow/prefer/require/verify-ca/verify-full)
  • Default: prefer
  • Example: sslmode=require

connectTimeout

  • Description: Connection timeout in seconds
  • Type: Integer
  • Default: 0 (no timeout)
  • Example: connectTimeout=10

loginTimeout

  • Description: Login timeout in seconds
  • Type: Integer
  • Default: 0 (no timeout)
  • Example: loginTimeout=5

socketTimeout

  • Description: Socket timeout in seconds
  • Type: Integer
  • Default: 0 (no timeout)
  • Example: socketTimeout=30

ApplicationName

  • Description: Application name for connection tracking
  • Type: String
  • Example: ApplicationName=MyApp

charSet

  • Description: Character set encoding
  • Type: String
  • Example: charSet=UTF-8

currentSchema

  • Description: Default schema for queries
  • Type: String
  • Example: currentSchema=myschema

tcpKeepAlive

  • Description: Enable TCP keep-alive
  • Type: Boolean (true/false)
  • Example: tcpKeepAlive=true

readOnly

  • Description: Read-only connection
  • Type: Boolean (true/false)
  • Example: readOnly=true

prepareThreshold

  • Description: Prepared statement threshold
  • Type: Integer
  • Example: prepareThreshold=5

reWriteBatchedInserts

  • Description: Rewrite batched inserts for better performance
  • Type: Boolean (true/false)
  • Example: reWriteBatchedInserts=true

Usage Examples

Basic JDBC URL Usage

import 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
    }
}

JDBC URL with Credentials

// Specify custom username and password
String jdbcUrl = "jdbc:tc:postgresql:15:///mydb?user=myuser&password=mypass";

try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
    // Use connection
}

JDBC URL with Version

// 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
}

JDBC URL with Initialization Script

// 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');

JDBC URL with Tmpfs (Performance)

// 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
}

JDBC URL with Daemon Mode

// 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
}

JDBC URL with PostgreSQL Parameters

// 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
}

Complete Example with All Options

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
    }
}

Integration with Test Frameworks

JUnit 4

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
        }
    }
}

JUnit 5

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);
        }
    }
}

Spring Boot Test

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
        }
    }
}

Provider Registration

The PostgreSQLContainerProvider is automatically registered via Java SPI mechanism. The registration is defined in:

META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider

This allows the Testcontainers JDBC driver to automatically detect and use the provider when it encounters jdbc:tc:postgresql: URLs.

Advantages and Limitations

Advantages

  • Simple Setup: No explicit container management code required
  • Automatic Lifecycle: Container starts on first connection, stops when connection closes
  • Framework Integration: Easy integration with Spring, Hibernate, and other frameworks
  • Configuration via URL: All configuration in the JDBC URL string
  • Minimal Dependencies: Only requires the JDBC driver

Limitations

  • Less Control: Cannot access container methods like getMappedPort() or getHost()
  • Single Container: Each unique URL creates a separate container
  • No Programmatic Configuration: All settings must be in URL string
  • Limited Debugging: Harder to debug container startup issues
  • No Direct Container Access: Cannot execute commands in container or get logs directly

When to Use

Use JDBC URL Pattern when:

  • Integration with frameworks that require JDBC URL (Spring, Hibernate)
  • Simple test scenarios without complex configuration
  • You want minimal boilerplate code
  • Container lifecycle management is not needed

Use PostgreSQLContainer class when:

  • Need access to container details (host, port, logs)
  • Require complex configuration or programmatic setup
  • Want explicit control over container lifecycle
  • Need to share container across multiple tests
  • Require custom wait strategies or health checks
  • Need to execute commands in container

Error Handling and Troubleshooting

Invalid JDBC URL Format

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";

Container Startup Failures

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;
}

Init Script Errors

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;
}

Connection Pooling with JDBC URL

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
    }
}

URL Parameter Validation

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
}

Debugging JDBC URL Issues

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;
}