or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdjdbc-container.mdjdbc-provider.mdr2dbc-support.md
tile.json

jdbc-provider.mddocs/

JDBC Provider API

The ClickHouseProvider class implements the service provider pattern for creating ClickHouse containers directly from JDBC URLs. This enables seamless integration with JDBC connection pools, frameworks, and any code that accepts standard JDBC URLs.

Key Information for Agents

Core Capabilities:

  • Database type support checking
  • Container creation with default or custom image tags
  • Automatic discovery via Java's ServiceLoader mechanism
  • JDBC URL-based container instantiation
  • URL parameter parsing (TC_IMAGE_TAG, TC_REUSABLE, TC_INITSCRIPT, etc.)

Key Methods:

  • supports(String databaseType): Check if provider supports database type
  • newInstance(): Create container with default image tag
  • newInstance(String tag): Create container with specific image tag

Default Behaviors:

  • Database type matching: Case-sensitive, must be exactly "clickhouse"
  • Default image tag: "24.12-alpine" (from clickhouse/clickhouse-server image)
  • Default username: "test"
  • Default password: "test"
  • Default database: Extracted from URL or defaults to "default"
  • Default ports: 8123 (HTTP), 9000 (native)
  • Default timeout: 120 seconds
  • ServiceLoader: Automatically discovered via META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider

Threading Model:

  • Provider instances are stateless (thread-safe)
  • supports() and newInstance() methods are thread-safe
  • Container instances created by provider follow same threading model as ClickHouseContainer

Lifecycle:

  • Provider is discovered automatically when JDBC URL starting with jdbc:tc:clickhouse: is used
  • Container is created when first connection is requested
  • Container is started automatically before connection is returned
  • Container cleanup is managed by Testcontainers (typically when JVM exits or connection pool closes)
  • With TC_REUSABLE=true, container persists across test runs

Exceptions:

  • IllegalArgumentException: Invalid parameters (null/empty tag, invalid database type)
  • SQLException: Invalid JDBC URL format, missing database type
  • ContainerLaunchException: Container fails to start (image pull failures, health check timeouts)
  • ClassNotFoundException: Missing JDBC driver on classpath

Edge Cases:

  • Null input to supports() returns false
  • Empty string to supports() returns false
  • Case-sensitive matching: "ClickHouse" != "clickhouse"
  • Whitespace is not trimmed: "clickhouse " returns false
  • Null tag throws IllegalArgumentException
  • Empty tag throws IllegalArgumentException
  • Invalid tags fail at start() time with ContainerLaunchException
  • Default tag may change between Testcontainers versions
  • Missing image tag in URL uses default tag
  • Invalid URL format throws SQLException with descriptive message
  • Missing database type in URL throws SQLException
  • Wrong database type uses different provider (not ClickHouse)
  • Username and password from JDBC URL are ignored (always uses container defaults)
  • Invalid parameters in URL are ignored (no error, parameter is skipped)
  • Container reuse requires testcontainers.reuse.enable=true in properties
  • Reuse enabled but property not set: reuse is silently ignored
  • Missing script file in TC_INITSCRIPT causes ContainerLaunchException

Capabilities

Database Type Support

Check if the provider supports a given database type. The check is case-sensitive.

/**
 * Check if this provider supports the specified database type
 * @param databaseType Database type identifier (e.g., "clickhouse")
 * @return true if database type is "clickhouse" (case-sensitive), false otherwise
 */
public boolean supports(String databaseType);

Usage Examples:

import org.testcontainers.containers.ClickHouseProvider;

ClickHouseProvider provider = new ClickHouseProvider();

boolean supportsClickHouse = provider.supports("clickhouse"); // Returns: true
boolean supportsPostgres = provider.supports("postgresql");   // Returns: false
boolean caseSensitive = provider.supports("ClickHouse");      // Returns: false (case-sensitive)

Edge Cases:

  • Null input returns false
  • Empty string returns false
  • Case-sensitive matching: "ClickHouse" != "clickhouse"
  • Whitespace is not trimmed: "clickhouse " returns false
  • Provider is stateless, can be called multiple times

Container Creation

Create new ClickHouse container instances with default or custom image tags. The provider is automatically discovered via Java's ServiceLoader mechanism.

/**
 * Create a new ClickHouse container instance with the default image tag
 * Uses tag "24.12-alpine" from clickhouse/clickhouse-server image
 * @return New JdbcDatabaseContainer instance (ClickHouseContainer)
 * @throws IllegalArgumentException if default tag cannot be determined
 */
public JdbcDatabaseContainer<?> newInstance();

/**
 * Create a new ClickHouse container instance with a specific image tag
 * @param tag Docker image tag to use (e.g., "24.12-alpine", "23.8")
 *           Full image name: clickhouse/clickhouse-server:{tag}
 * @return New JdbcDatabaseContainer instance (ClickHouseContainer)
 * @throws IllegalArgumentException if tag is null or empty
 */
public JdbcDatabaseContainer<?> newInstance(String tag);

Usage Examples:

import org.testcontainers.containers.ClickHouseProvider;
import org.testcontainers.containers.JdbcDatabaseContainer;

ClickHouseProvider provider = new ClickHouseProvider();

// Create container with default tag (24.12-alpine)
JdbcDatabaseContainer<?> container1 = provider.newInstance();
container1.start();

// Create container with specific tag
JdbcDatabaseContainer<?> container2 = provider.newInstance("23.8-alpine");
container2.start();

// Create container with latest tag (not recommended for tests)
JdbcDatabaseContainer<?> container3 = provider.newInstance("latest");
container3.start();

Edge Cases:

  • Null tag throws IllegalArgumentException
  • Empty tag throws IllegalArgumentException
  • Invalid tags (e.g., "nonexistent") fail at start() time with ContainerLaunchException
  • Default tag may change between Testcontainers versions
  • Container must be started with start() before use
  • Container follows same lifecycle as ClickHouseContainer

JDBC URL-Based Instantiation

The provider enables automatic container creation from JDBC URLs using the Testcontainers JDBC URL format. This pattern is useful for integration with connection pools and frameworks. The provider is automatically invoked when a JDBC URL starting with jdbc:tc:clickhouse: is used.

URL Format

jdbc:tc:clickhouse://[host]/[database]?[parameters]

Components:

  • jdbc:tc: - Testcontainers JDBC URL prefix (required)
  • clickhouse - Database type identifier (required, case-sensitive)
  • host - Ignored (container host is determined automatically, can be any value)
  • database - Database name to use (optional, defaults to "default")
  • parameters - Optional URL parameters (see below)

Common URL Parameters

Testcontainers Parameters:

  • TC_IMAGE_TAG - Specify Docker image tag (e.g., ?TC_IMAGE_TAG=24.12-alpine)
  • TC_REUSABLE - Enable container reuse (e.g., ?TC_REUSABLE=true)
  • TC_INITSCRIPT - Path to initialization script (e.g., ?TC_INITSCRIPT=init.sql)
  • TC_INITFUNCTION - Function to call for initialization (advanced)

ClickHouse JDBC Parameters:

  • clickhouse_setting_* - ClickHouse session settings (e.g., ?clickhouse_setting_max_result_rows=1000)
  • Standard JDBC parameters (user, password, etc.) are ignored (use container defaults)

Parameter Parsing:

  • Parameters are URL-decoded automatically
  • Multiple parameters are separated by &
  • Parameter values are URL-encoded if needed
  • Duplicate parameters: last value wins
  • Parameters are case-sensitive

Usage Examples

Basic URL-based instantiation:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class ClickHouseJdbcUrlTest {
    public void testWithJdbcUrl() throws Exception {
        // Create connection using Testcontainers JDBC URL
        // Container is automatically created, started, and cleaned up
        String jdbcUrl = "jdbc:tc:clickhouse://hostname/testdb";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, "test", "test");
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT 1")) {

            rs.next();
            int result = rs.getInt(1);
            System.out.println("Result: " + result);
        }
    }
}

URL with custom image tag:

import java.sql.Connection;
import java.sql.DriverManager;

public class ClickHouseCustomTagTest {
    public void testWithCustomTag() throws Exception {
        // Specify custom Docker image tag
        String jdbcUrl = "jdbc:tc:clickhouse://hostname/testdb?TC_IMAGE_TAG=23.8-alpine";

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

URL with container reuse:

import java.sql.Connection;
import java.sql.DriverManager;

public class ClickHouseReusableTest {
    public void testWithReuse() throws Exception {
        // Enable container reuse for faster test execution
        // Requires testcontainers.reuse.enable=true in ~/.testcontainers.properties
        String jdbcUrl = "jdbc:tc:clickhouse://hostname/testdb?TC_IMAGE_TAG=24.12-alpine&TC_REUSABLE=true";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, "test", "test")) {
            // Container will be reused across test runs if configuration matches
        }
    }
}

URL with initialization script:

import java.sql.Connection;
import java.sql.DriverManager;

public class ClickHouseInitScriptTest {
    public void testWithInitScript() throws Exception {
        // Execute initialization script on container startup
        String jdbcUrl = "jdbc:tc:clickhouse://hostname/testdb?TC_IMAGE_TAG=24.12-alpine&TC_INITSCRIPT=init.sql";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, "test", "test")) {
            // Schema and data from init.sql are already loaded
        }
    }
}

URL with ClickHouse settings:

import java.sql.Connection;
import java.sql.DriverManager;

public class ClickHouseSettingsTest {
    public void testWithSettings() throws Exception {
        // Combine Testcontainers and ClickHouse parameters
        String jdbcUrl = "jdbc:tc:clickhouse://hostname/testdb?" +
                         "TC_IMAGE_TAG=24.12-alpine&" +
                         "clickhouse_setting_max_result_rows=5000&" +
                         "clickhouse_setting_max_execution_time=60";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, "test", "test")) {
            // Connection has custom ClickHouse settings applied
        }
    }
}

URL with multiple parameters:

import java.sql.Connection;
import java.sql.DriverManager;

public class ClickHouseMultipleParamsTest {
    public void testWithMultipleParams() throws Exception {
        // Multiple parameters combined
        String jdbcUrl = "jdbc:tc:clickhouse://hostname/mydb?" +
                         "TC_IMAGE_TAG=24.12-alpine&" +
                         "TC_REUSABLE=true&" +
                         "TC_INITSCRIPT=schema.sql&" +
                         "clickhouse_setting_max_result_rows=10000";

        try (Connection conn = DriverManager.getConnection(jdbcUrl, "test", "test")) {
            // Container configured with all parameters
        }
    }
}

Integration with connection pools:

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class ClickHouseConnectionPoolTest {
    public DataSource createDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:tc:clickhouse://hostname/testdb?TC_IMAGE_TAG=24.12-alpine");
        config.setUsername("test");
        config.setPassword("test");
        config.setMaximumPoolSize(10);
        config.setMinimumIdle(2);

        return new HikariDataSource(config);
    }

    public void testWithConnectionPool() throws SQLException {
        DataSource dataSource = createDataSource();

        try (Connection conn = dataSource.getConnection()) {
            // Use pooled connection with Testcontainers-managed ClickHouse
            // Container is created when first connection is requested
        }
        // Container cleanup is managed by Testcontainers
    }
}

Spring Boot integration:

import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import javax.sql.DataSource;

@Configuration
@Profile("test")
public class TestDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:tc:clickhouse://hostname/testdb?TC_IMAGE_TAG=24.12-alpine&TC_REUSABLE=true")
                .username("test")
                .password("test")
                .build();
    }
}

Spring Boot with Dynamic Properties:

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.clickhouse.ClickHouseContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers
public class SpringBootIntegrationTest {
    @Container
    static ClickHouseContainer clickhouse = new ClickHouseContainer("clickhouse/clickhouse-server:24.12-alpine");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", clickhouse::getJdbcUrl);
        registry.add("spring.datasource.username", clickhouse::getUsername);
        registry.add("spring.datasource.password", clickhouse::getPassword);
    }

    // Tests use Spring-managed DataSource with Testcontainers
}

JPA/Hibernate integration:

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@SpringBootTest
@Testcontainers
public class JpaIntegrationTest {
    @Container
    static ClickHouseContainer clickhouse = new ClickHouseContainer("clickhouse/clickhouse-server:24.12-alpine");

    @PersistenceContext
    private EntityManager entityManager;

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", clickhouse::getJdbcUrl);
        registry.add("spring.datasource.username", clickhouse::getUsername);
        registry.add("spring.datasource.password", clickhouse::getPassword);
    }

    // Use JPA with Testcontainers-managed ClickHouse
}

Implementation Details

The ClickHouseProvider class is automatically discovered by the Testcontainers JDBC module through Java's ServiceLoader mechanism. When a JDBC URL starting with jdbc:tc:clickhouse: is used, the provider:

  1. Detects the ClickHouse database type from the URL
  2. Parses URL parameters (TC_IMAGE_TAG, TC_REUSABLE, etc.)
  3. Creates a new ClickHouseContainer instance with the specified configuration
  4. Starts the container automatically
  5. Returns a JDBC connection to the running container
  6. Manages container lifecycle based on connection lifecycle

ServiceLoader Configuration: The provider is registered in META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider with the class name org.testcontainers.containers.ClickHouseProvider.

Container Lifecycle:

  • Container is created when first connection is requested
  • Container is started automatically before connection is returned
  • Container cleanup is managed by Testcontainers (typically when JVM exits or connection pool closes)
  • With TC_REUSABLE=true, container persists across test runs

Error Handling:

  • Invalid URLs throw SQLException with descriptive message
  • Missing image tags cause ContainerLaunchException during container start
  • Invalid parameters are ignored (no error, parameter is skipped)

Default Configuration

When creating containers via the provider:

  • Default Image: clickhouse/clickhouse-server:24.12-alpine
  • Default Username: test
  • Default Password: test
  • Default Database: Extracted from URL or defaults to default
  • Default Ports: 8123 (HTTP), 9000 (native)
  • Default Timeout: 120 seconds

URL Parameter Defaults:

  • If TC_IMAGE_TAG is not specified, uses 24.12-alpine
  • If TC_REUSABLE is not specified, reuse is disabled
  • If database name is not specified in URL, uses default
  • Username and password from JDBC URL are ignored (always uses container defaults)

Edge Cases and Error Scenarios

Invalid URL Format:

// Missing database type
String url = "jdbc:tc://hostname/testdb"; // Throws SQLException

// Wrong database type
String url = "jdbc:tc:postgresql://hostname/testdb"; // Uses PostgreSQL provider, not ClickHouse

// Missing protocol prefix
String url = "clickhouse://hostname/testdb"; // Not recognized as Testcontainers URL

Missing Image Tag:

// If default tag cannot be determined
String url = "jdbc:tc:clickhouse://hostname/testdb";
// May throw ContainerLaunchException if default tag resolution fails

Container Reuse Without Property:

// TC_REUSABLE=true in URL but testcontainers.reuse.enable=false in properties
String url = "jdbc:tc:clickhouse://hostname/testdb?TC_REUSABLE=true";
// Reuse is silently ignored, new container is created

Initialization Script Errors:

// Missing script file
String url = "jdbc:tc:clickhouse://hostname/testdb?TC_INITSCRIPT=nonexistent.sql";
// Throws ContainerLaunchException during container start

Parameter Parsing:

// Invalid parameters are ignored
String url = "jdbc:tc:clickhouse://hostname/testdb?INVALID_PARAM=value";
// Parameter is ignored, no error

// Duplicate parameters: last value wins
String url = "jdbc:tc:clickhouse://hostname/testdb?TC_IMAGE_TAG=23.8&TC_IMAGE_TAG=24.12";
// Uses 24.12 (last value)

Username and Password Ignored:

// Username and password from JDBC URL are ignored
String url = "jdbc:tc:clickhouse://hostname/testdb?user=custom&password=custom";
// Always uses container defaults: test/test

Complete Example

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.SQLException;

public class ClickHouseProviderExample {

    public void demonstrateJdbcUrlUsage() throws Exception {
        // Construct Testcontainers JDBC URL with full configuration
        String jdbcUrl = "jdbc:tc:clickhouse://localhost/mydb?" +
                         "TC_IMAGE_TAG=24.12-alpine&" +
                         "TC_REUSABLE=true&" +
                         "TC_INITSCRIPT=schema.sql&" +
                         "clickhouse_setting_max_execution_time=60";

        // Connect using the Testcontainers URL
        // Container is created and started automatically
        try (Connection conn = DriverManager.getConnection(jdbcUrl, "test", "test")) {

            // Create table (if not in init script)
            try (Statement stmt = conn.createStatement()) {
                stmt.execute(
                    "CREATE TABLE IF NOT EXISTS events (" +
                    "  id UInt32, " +
                    "  timestamp DateTime, " +
                    "  message String" +
                    ") ENGINE = MergeTree() ORDER BY (id, timestamp)"
                );
            }

            // Insert data
            try (PreparedStatement stmt = conn.prepareStatement(
                    "INSERT INTO events (id, timestamp, message) VALUES (?, now(), ?)")) {
                stmt.setInt(1, 1);
                stmt.setString(2, "Test event");
                stmt.execute();
            }

            // Query data
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT count(*) as total FROM events")) {
                if (rs.next()) {
                    System.out.println("Total events: " + rs.getInt("total"));
                }
            }
        } catch (SQLException e) {
            System.err.println("Database error: " + e.getMessage());
            throw e;
        }
        // Container is automatically stopped when connection is closed
    }

    public void demonstrateProviderDirectUsage() throws Exception {
        ClickHouseProvider provider = new ClickHouseProvider();

        // Create container using provider
        JdbcDatabaseContainer<?> container = provider.newInstance("24.12-alpine");

        try {
            container.start();

            String jdbcUrl = container.getJdbcUrl();
            String username = container.getUsername();
            String password = container.getPassword();

            try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) {
                // Use connection...
            }
        } catch (ContainerLaunchException e) {
            System.err.println("Container failed to start: " + e.getMessage());
            throw e;
        } finally {
            container.stop();
        }
    }

    public void demonstrateErrorHandling() {
        try {
            // Invalid URL format
            String invalidUrl = "jdbc:tc://hostname/testdb";
            DriverManager.getConnection(invalidUrl, "test", "test");
        } catch (SQLException e) {
            System.err.println("Expected error for invalid URL: " + e.getMessage());
        }

        try {
            // Missing image (will fail at container start)
            String url = "jdbc:tc:clickhouse://hostname/testdb?TC_IMAGE_TAG=nonexistent:tag";
            DriverManager.getConnection(url, "test", "test");
        } catch (SQLException e) {
            System.err.println("Expected error for missing image: " + e.getMessage());
        }
    }
}