Testcontainers JDBC driver that provides lightweight, throwaway database instances for testing
The JdbcDatabaseContainerProvider system uses the Service Provider Interface (SPI) pattern to create database-specific container implementations based on JDBC URL database types.
Abstract base class for database container providers that integrate with the JDBC driver's discovery mechanism.
/**
* Base class for classes that can provide a JDBC container
* Discovered via ServiceLoader mechanism
*/
public abstract class JdbcDatabaseContainerProvider {
}Method to determine if a provider can handle a specific database type.
/**
* Test if the specified database type is supported by this provider
* Should match the base image name from JDBC URL
* @param databaseType database type extracted from JDBC URL (e.g., "mysql", "postgresql")
* @return true if provider can handle this database type, false otherwise
*/
public abstract boolean supports(String databaseType);Usage Example:
public class MySQLContainerProvider extends JdbcDatabaseContainerProvider {
@Override
public boolean supports(String databaseType) {
return "mysql".equals(databaseType);
}
}Methods for creating container instances with different configurations.
/**
* Create new container instance with default tag
* Subclasses should override to provide stable default instead of "latest"
* @return new JdbcDatabaseContainer instance
*/
public JdbcDatabaseContainer newInstance();
/**
* Create new container instance with specified image tag
* @param tag Docker image tag to use
* @return new JdbcDatabaseContainer instance
*/
public abstract JdbcDatabaseContainer newInstance(String tag);
/**
* Create new container instance using ConnectionUrl information
* Automatically extracts image tag and reusability settings from URL
* @param url parsed ConnectionUrl with configuration
* @return configured JdbcDatabaseContainer instance
*/
public JdbcDatabaseContainer newInstance(ConnectionUrl url);Implementation Example:
public class PostgreSQLContainerProvider extends JdbcDatabaseContainerProvider {
@Override
public boolean supports(String databaseType) {
return "postgresql".equals(databaseType);
}
@Override
public JdbcDatabaseContainer newInstance() {
return newInstance("13"); // Use stable default instead of "latest"
}
@Override
public JdbcDatabaseContainer newInstance(String tag) {
return new PostgreSQLContainer<>("postgres:" + tag);
}
}Protected helper method for creating instances from connection URLs with standard parameter mapping.
/**
* Helper method to create container instance from ConnectionUrl
* Handles common parameter extraction and container configuration
* @param connectionUrl parsed connection URL
* @param userParamName query parameter name for username (e.g., "user")
* @param pwdParamName query parameter name for password (e.g., "password")
* @return configured JdbcDatabaseContainer instance
* @throws NullPointerException if connectionUrl is null
*/
protected JdbcDatabaseContainer newInstanceFromConnectionUrl(
ConnectionUrl connectionUrl,
String userParamName,
String pwdParamName
);Usage in Provider Implementation:
public class MySQLContainerProvider extends JdbcDatabaseContainerProvider {
@Override
public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {
// Use helper method with MySQL-specific parameter names
return newInstanceFromConnectionUrl(connectionUrl, "user", "password");
}
}The JDBC driver discovers providers using Java's ServiceLoader mechanism:
JdbcDatabaseContainerProviderMETA-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvidersupports() method to find matchesTo register a custom provider, create a file in your JAR:
File: src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider
Content:
com.example.CustomDatabaseContainerProvider
com.example.AnotherDatabaseContainerProviderExample of a complete custom provider:
package com.example;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.JdbcDatabaseContainerProvider;
import org.testcontainers.jdbc.ConnectionUrl;
public class CustomDatabaseContainerProvider extends JdbcDatabaseContainerProvider {
@Override
public boolean supports(String databaseType) {
return "customdb".equals(databaseType);
}
@Override
public JdbcDatabaseContainer newInstance() {
return newInstance("1.0"); // Stable default version
}
@Override
public JdbcDatabaseContainer newInstance(String tag) {
return new CustomDatabaseContainer("customdb:" + tag);
}
@Override
public JdbcDatabaseContainer newInstance(ConnectionUrl url) {
final JdbcDatabaseContainer container;
if (url.getImageTag().isPresent()) {
container = newInstance(url.getImageTag().get());
} else {
container = newInstance();
}
// Configure reusability from URL
container.withReuse(url.isReusable());
// Extract database name, username, password from URL parameters
String dbName = url.getDatabaseName().orElse("defaultdb");
String user = url.getQueryParameters().getOrDefault("user", "test");
String password = url.getQueryParameters().getOrDefault("password", "test");
return container
.withDatabaseName(dbName)
.withUsername(user)
.withPassword(password);
}
}Testcontainers includes providers for common databases:
mysqlmysql:8.0user, passwordpostgresqlpostgres:13user, passwordoracleuser, passwordsqlservermcr.microsoft.com/mssql/serveruser, passwordWhen the JDBC driver encounters a jdbc:tc: URL:
mysql from jdbc:tc:mysql:8.0://...)supports(databaseType) on each provider until match foundUnsupportedOperationExceptionAlways override newInstance() to provide a stable default version instead of "latest":
@Override
public JdbcDatabaseContainer newInstance() {
// Good: stable version
return newInstance("13.7");
// Avoid: unpredictable "latest"
// return newInstance("latest");
}Use the helper method newInstanceFromConnectionUrl() for consistent parameter extraction:
@Override
public JdbcDatabaseContainer newInstance(ConnectionUrl url) {
return newInstanceFromConnectionUrl(url, "user", "password");
}Validate image tags when possible to prevent runtime errors:
@Override
public JdbcDatabaseContainer newInstance(String tag) {
if (tag == null || tag.trim().isEmpty()) {
throw new IllegalArgumentException("Image tag cannot be null or empty");
}
return new MySQLContainer<>("mysql:" + tag);
}Ensure containers are properly configured for resource cleanup:
@Override
public JdbcDatabaseContainer newInstance(String tag) {
return new MySQLContainer<>("mysql:" + tag)
.withReuse(false) // Explicit cleanup unless URL specifies reuse
.withStartupTimeout(Duration.ofMinutes(2));
}Install with Tessl CLI
npx tessl i tessl/maven-org-testcontainers--jdbc