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.
Core Capabilities:
Key Methods:
supports(String databaseType): Check if provider supports database typenewInstance(): Create container with default image tagnewInstance(String tag): Create container with specific image tagDefault Behaviors:
META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProviderThreading Model:
supports() and newInstance() methods are thread-safeClickHouseContainerLifecycle:
jdbc:tc:clickhouse: is usedTC_REUSABLE=true, container persists across test runsExceptions:
IllegalArgumentException: Invalid parameters (null/empty tag, invalid database type)SQLException: Invalid JDBC URL format, missing database typeContainerLaunchException: Container fails to start (image pull failures, health check timeouts)ClassNotFoundException: Missing JDBC driver on classpathEdge Cases:
supports() returns falsesupports() returns falsefalseIllegalArgumentExceptionIllegalArgumentExceptionstart() time with ContainerLaunchExceptionSQLException with descriptive messageSQLExceptiontestcontainers.reuse.enable=true in propertiesTC_INITSCRIPT causes ContainerLaunchExceptionCheck 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:
falsefalsefalseCreate 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:
IllegalArgumentExceptionIllegalArgumentExceptionstart() time with ContainerLaunchExceptionstart() before useClickHouseContainerThe 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.
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)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)Parameter Parsing:
&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
}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:
ClickHouseContainer instance with the specified configurationServiceLoader Configuration:
The provider is registered in META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider with the class name org.testcontainers.containers.ClickHouseProvider.
Container Lifecycle:
TC_REUSABLE=true, container persists across test runsError Handling:
SQLException with descriptive messageContainerLaunchException during container startWhen creating containers via the provider:
clickhouse/clickhouse-server:24.12-alpinetesttestdefaultURL Parameter Defaults:
TC_IMAGE_TAG is not specified, uses 24.12-alpineTC_REUSABLE is not specified, reuse is disableddefaultInvalid 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 URLMissing Image Tag:
// If default tag cannot be determined
String url = "jdbc:tc:clickhouse://hostname/testdb";
// May throw ContainerLaunchException if default tag resolution failsContainer 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 createdInitialization Script Errors:
// Missing script file
String url = "jdbc:tc:clickhouse://hostname/testdb?TC_INITSCRIPT=nonexistent.sql";
// Throws ContainerLaunchException during container startParameter 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/testimport 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());
}
}
}