Java library for integration testing with Docker containers
npx @tessl/cli install tessl/maven-org-testcontainers--testcontainers@2.0.0Testcontainers is a Java library that provides lightweight, throwaway Docker container instances for integration testing. It enables developers to write tests that use real dependencies (databases, message brokers, web services) running in Docker containers, ensuring tests run in environments that closely mirror production. Testcontainers manages the complete container lifecycle automatically, from pulling images to cleaning up resources after tests complete.
pom.xml:<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>2.0.3</version>
<scope>test</scope>
</dependency>Or for Gradle:
testImplementation 'org.testcontainers:testcontainers:2.0.3'import org.testcontainers.Testcontainers;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.lifecycle.Startables;
import org.testcontainers.utility.DockerImageName;import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;
import org.junit.jupiter.api.Test;
public class RedisBackedCacheTest {
@Test
public void testSimpleRedisUsage() {
// Create a Redis container
try (GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:7.0-alpine"))
.withExposedPorts(6379)) {
// Start the container
redis.start();
// Get connection details
String host = redis.getHost();
Integer port = redis.getMappedPort(6379);
// Use the Redis instance in your test
// RedisClient client = RedisClient.create("redis://" + host + ":" + port);
// ... test code here ...
} // Container is automatically stopped and removed
}
}More complex example with configuration:
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;
public class PostgresDatabaseTest {
@Test
public void testDatabaseConnection() {
try (GenericContainer<?> postgres = new GenericContainer<>(DockerImageName.parse("postgres:15"))
.withExposedPorts(5432)
.withEnv("POSTGRES_PASSWORD", "test")
.withEnv("POSTGRES_DB", "testdb")
.waitingFor(Wait.forListeningPort())
.withStartupTimeout(Duration.ofSeconds(60))) {
postgres.start();
String jdbcUrl = String.format("jdbc:postgresql://%s:%d/testdb",
postgres.getHost(),
postgres.getMappedPort(5432));
// Connect and run tests
}
}
}| Operation | Method | Example |
|---|---|---|
| Expose ports | withExposedPorts(Integer...) | .withExposedPorts(8080, 8081) |
| Set environment | withEnv(String, String) | .withEnv("DB_NAME", "test") |
| Run command | withCommand(String...) | .withCommand("sh", "-c", "sleep 10") |
| Copy files | withCopyFileToContainer(MountableFile, String) | .withCopyFileToContainer(file, "/app/config") |
| Wait for ready | waitingFor(WaitStrategy) | .waitingFor(Wait.forHttp("/health")) |
| Set timeout | withStartupTimeout(Duration) | .withStartupTimeout(Duration.ofMinutes(2)) |
| Strategy | Use Case | Example |
|---|---|---|
Wait.forListeningPort() | Service listening on port | Redis, PostgreSQL |
Wait.forHttp(path) | HTTP endpoint available | Web applications, REST APIs |
Wait.forLogMessage(regex, times) | Log message appears | Application startup messages |
Wait.forHealthcheck() | Docker healthcheck passes | Images with HEALTHCHECK |
All container operations can throw the following exceptions:
// Common exceptions to handle
org.testcontainers.containers.ContainerLaunchException // Container failed to start
org.testcontainers.containers.ContainerFetchException // Failed to pull image
java.util.concurrent.TimeoutException // Startup timeout exceededExample with error handling:
try {
GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse("myapp:latest"))
.withExposedPorts(8080)
.waitingFor(Wait.forHttp("/health").forStatusCode(200))
.withStartupTimeout(Duration.ofSeconds(30));
container.start();
// Use container
} catch (ContainerLaunchException e) {
// Container failed to start - check logs
System.err.println("Container logs: " + container.getLogs());
throw e;
} catch (TimeoutException e) {
// Startup took too long - check wait strategy
System.err.println("Container did not become ready in time");
throw e;
}Testcontainers follows a layered architecture with clear separation of concerns:
Core Container Management Layer:
GenericContainer: The primary class for creating and managing Docker containers. Uses a fluent API with self-referential generics (SELF extends GenericContainer<SELF>) for type-safe method chaining in subclasses.
Container Lifecycle: Implements Startable and AutoCloseable interfaces for proper resource management. Provides lifecycle hooks (configure(), containerIsCreated(), containerIsStarting(), containerIsStarted()) for customization in subclasses.
Readiness Detection Layer:
WaitStrategy interface allows different approaches (port listening, HTTP endpoints, log messages, healthchecks) to detect when containers are ready for testing. Keeps readiness logic decoupled from container lifecycle.Docker Integration Layer:
Image Management: Abstraction layer for Docker images supporting remote images, locally built images, and Jib-based builds. The ImagePullPolicy interface controls when images are pulled from registries.
Docker Client Abstraction: Strategy pattern for Docker client configuration (DockerClientProviderStrategy) supporting multiple Docker hosts (Unix socket, Windows named pipe, Docker Desktop, Docker Machine, environment variables). Isolates environment-specific Docker connectivity from container logic.
Network & Communication Layer:
Network interface represents Docker networks, enabling containers to communicate using network aliases and service discovery patterns.Resource Management Layer:
ResourceReaper automatically cleans up containers, networks, and images at JVM shutdown, preventing resource leaks even when tests fail. Handles cleanup orthogonally to container lifecycle.API Stability:
@UnstableAPI annotation to mark APIs that are subject to change and should not be considered stable. These APIs may change in future versions without following semantic versioning guarantees. When using APIs marked with @UnstableAPI, be prepared to adapt your code during library upgrades.Utility class for exposing host ports to containers, enabling containers to access services running on the host machine. This is particularly useful when containers need to connect to databases, APIs, or other services running on the Docker host.
/**
* Utility class for exposing host ports to containers.
* This is a utility class with static methods only and cannot be instantiated.
*/
public class Testcontainers {
/**
* Expose one or more host ports to containers.
* Makes the specified ports on the host accessible from within containers
* using the special hostname 'host.testcontainers.internal'.
*
* @param ports the host ports to expose
*/
public static void exposeHostPorts(int... ports);
/**
* Expose host ports to containers with custom port mapping.
* Maps host ports to different container-side ports.
*
* @param ports map of host port to container port mappings
*/
public static void exposeHostPorts(Map<Integer, Integer> ports);
}Usage Examples:
import org.testcontainers.Testcontainers;
import org.testcontainers.containers.GenericContainer;
// Expose host port for container to access host service
Testcontainers.exposeHostPorts(8080);
// Container can now access host service at host.testcontainers.internal:8080
GenericContainer<?> container = new GenericContainer<>("myapp:latest")
.withEnv("API_URL", "http://host.testcontainers.internal:8080/api")
.withExposedPorts(3000);
container.start();Common Use Cases:
// Host PostgreSQL running on port 5432
Testcontainers.exposeHostPorts(5432);
GenericContainer<?> app = new GenericContainer<>("myapp:latest")
.withEnv("DB_HOST", "host.testcontainers.internal")
.withEnv("DB_PORT", "5432");// Local development API running on port 8000
Testcontainers.exposeHostPorts(8000);
GenericContainer<?> testRunner = new GenericContainer<>("test-suite:latest")
.withEnv("API_BASE_URL", "http://host.testcontainers.internal:8000");// Expose multiple services
Testcontainers.exposeHostPorts(5432, 6379, 8080);// Map host port 3000 to container-side port 80
Map<Integer, Integer> portMap = Map.of(3000, 80, 5432, 5432);
Testcontainers.exposeHostPorts(portMap);
// Container accesses host:3000 as host.testcontainers.internal:80Important Notes:
host.testcontainers.internal resolves to the Docker host from within containersUtility for starting multiple containers in parallel with automatic dependency resolution. The Startables class enables efficient parallel startup of container groups, significantly reducing test execution time when working with multiple containers.
/**
* Utilities for managing multiple Startable resources.
* This is a utility class with static methods only and cannot be instantiated.
*/
public class Startables {
/**
* Start multiple resources, respecting their dependencies.
* Containers are started in parallel where possible, with automatic
* dependency resolution ensuring correct startup order.
*
* @param startables resources to start
* @return CompletableFuture that completes when all are started
*/
public static CompletableFuture<Void> deepStart(Startable... startables);
/**
* Start a collection of resources, respecting their dependencies.
*
* @param startables resources to start
* @return CompletableFuture that completes when all are started
*/
public static CompletableFuture<Void> deepStart(Collection<? extends Startable> startables);
/**
* Start a stream of resources asynchronously, respecting dependencies.
*
* @param startables stream of resources to start
* @return CompletableFuture that completes when all are started
*/
public static CompletableFuture<Void> deepStart(Stream<? extends Startable> startables);
}Usage Example:
import org.testcontainers.lifecycle.Startables;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.DockerImageName;
// Create multiple containers with dependencies
Network network = Network.newNetwork();
GenericContainer<?> database = new GenericContainer<>(DockerImageName.parse("postgres:15"))
.withNetwork(network)
.withNetworkAliases("db")
.withExposedPorts(5432);
GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:7.0"))
.withNetwork(network)
.withNetworkAliases("cache")
.withExposedPorts(6379);
GenericContainer<?> app = new GenericContainer<>(DockerImageName.parse("myapp:latest"))
.withNetwork(network)
.withExposedPorts(8080)
.dependsOn(database, redis); // Explicit dependencies
// Start all containers in parallel (database and redis start together, app waits for them)
Startables.deepStart(app).join();
// All containers are now running with correct startup order
String appUrl = String.format("http://%s:%d", app.getHost(), app.getMappedPort(8080));Performance Benefits:
// Without Startables (sequential startup - slow)
database.start(); // ~5 seconds
redis.start(); // ~3 seconds
app.start(); // ~10 seconds
// Total: ~18 seconds
// With Startables (parallel startup - fast)
Startables.deepStart(database, redis, app).join();
// Total: ~10 seconds (database and redis start in parallel)Common Use Cases:
// Start multiple databases in parallel for testing
GenericContainer<?> postgres = new GenericContainer<>("postgres:15");
GenericContainer<?> mysql = new GenericContainer<>("mysql:8");
GenericContainer<?> mongodb = new GenericContainer<>("mongo:6");
Startables.deepStart(postgres, mysql, mongodb).join();
// All three start simultaneouslyGenericContainer<?> messageQueue = new GenericContainer<>("rabbitmq:3");
GenericContainer<?> database = new GenericContainer<>("postgres:15");
GenericContainer<?> userService = new GenericContainer<>("user-service:latest")
.dependsOn(database, messageQueue);
GenericContainer<?> orderService = new GenericContainer<>("order-service:latest")
.dependsOn(database, messageQueue);
// Database and message queue start in parallel, then both services start in parallel
Startables.deepStart(userService, orderService).join();Control test and container lifecycle with interfaces for starting, stopping, and test integration. The Startable interface provides lifecycle management for containers and resources.
/**
* Interface for lifecycle-managed resources.
*/
public interface Startable {
void start();
void stop();
}
/**
* Interface for test-aware resources.
*/
public interface TestLifecycleAware {
void beforeTest(TestDescription description);
void afterTest(TestDescription description, Optional<Throwable> throwable);
}Key Use Cases:
Seamless test framework integration with automatic container lifecycle management. The JUnit Jupiter extension eliminates boilerplate by automatically starting and stopping containers based on test annotations.
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.junit.jupiter.Container;
/**
* Activates automatic container management for test classes.
* Containers annotated with @Container are started/stopped automatically.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testcontainers {
/**
* Whether tests should be disabled when Docker is unavailable.
* Default: false (tests fail if Docker unavailable)
*/
boolean disabledWithoutDocker() default false;
/**
* Whether containers should start in parallel.
* Default: false (sequential startup)
*/
boolean parallel() default false;
}
/**
* Marks container fields for automatic lifecycle management.
* Static fields: shared across all tests
* Instance fields: restarted per test
*/
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Container {
}Usage Example:
@Testcontainers
class DatabaseTest {
// Shared container (started once)
@Container
private static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
// Per-test container (restarted for each test)
@Container
private GenericContainer<?> redis = new GenericContainer<>("redis:7.0");
@Test
void test() {
// Containers automatically started and will be stopped
}
}Pre-configured container implementations for popular databases, message queues, and services. Modules provide simplified APIs with service-specific configuration methods and connection helpers.
/**
* PostgreSQL database container with JDBC support.
* Default port: 5432, database: test, user: test
*/
public class PostgreSQLContainer<SELF extends PostgreSQLContainer<SELF>>
extends JdbcDatabaseContainer<SELF> {
public String getJdbcUrl(); // jdbc:postgresql://host:port/database
public String getUsername(); // Default: "test"
public String getPassword(); // Default: "test"
public String getDatabaseName(); // Default: "test"
public SELF withDatabaseName(String databaseName);
public SELF withUsername(String username);
public SELF withPassword(String password);
public SELF withInitScript(String initScriptPath);
}
/**
* MySQL database container with JDBC support.
* Default port: 3306, database: test, user: test
*/
public class MySQLContainer<SELF extends MySQLContainer<SELF>>
extends JdbcDatabaseContainer<SELF> {
public String getJdbcUrl(); // Includes useSSL=false automatically
public String getDriverClassName(); // com.mysql.cj.jdbc.Driver
public SELF withConfigurationOverride(String configurationPath);
}
/**
* MongoDB NoSQL database container.
* Default port: 27017, no authentication
*/
public class MongoDBContainer extends GenericContainer<MongoDBContainer> {
public String getConnectionString(); // mongodb://host:port
public String getReplicaSetUrl(); // For transactions
public MongoDBContainer withReplicaSet(); // Enable replica set mode
}Available Modules:
Usage Example:
// PostgreSQL with JDBC
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("mydb")
.withInitScript("schema.sql");
postgres.start();
Connection conn = DriverManager.getConnection(
postgres.getJdbcUrl(),
postgres.getUsername(),
postgres.getPassword()
);
// MongoDB
MongoDBContainer mongodb = new MongoDBContainer("mongo:7.0")
.withReplicaSet(); // For transactions
mongodb.start();
MongoClient client = MongoClients.create(mongodb.getConnectionString());Container Lifecycle and Configuration
Core container management functionality including creation, configuration, startup, and cleanup. The GenericContainer class provides a fluent API for configuring all aspects of container behavior.
public class GenericContainer<SELF extends GenericContainer<SELF>>
implements Container<SELF>, AutoCloseable, WaitStrategyTarget, Startable {
// Configuration methods
public SELF withImagePullPolicy(ImagePullPolicy policy);
public SELF withExposedPorts(Integer... ports);
public SELF withEnv(String key, String value);
public SELF withCommand(String... command);
public SELF withNetwork(Network network);
public SELF withNetworkAliases(String... aliases);
public SELF waitingFor(WaitStrategy waitStrategy);
// Lifecycle methods
public void start(); // Throws: ContainerLaunchException
public void stop();
public void close();
// Runtime access methods
public String getHost();
public Integer getMappedPort(int originalPort); // Throws: IllegalArgumentException if port not exposed
public boolean isRunning();
}Key Parameters:
withStartupTimeout(Duration) - Default: 60 seconds. Maximum time to wait for container startup.withExposedPorts(Integer...) - No default. Must specify ports to access from host.withEnv(String, String) - Environment variables. String values only; no default.Common Pitfalls:
getMappedPort() - throws IllegalArgumentExceptionTimeoutExceptionContainer Lifecycle and Configuration
Comprehensive strategies for determining when containers are ready for testing. The Wait factory class provides convenient methods for creating common wait strategies.
public class Wait {
public static WaitStrategy defaultWaitStrategy();
public static HostPortWaitStrategy forListeningPort();
public static HostPortWaitStrategy forListeningPorts(int... ports);
public static HttpWaitStrategy forHttp(String path);
public static HttpWaitStrategy forHttps(String path);
public static LogMessageWaitStrategy forLogMessage(String regex, int times);
public static DockerHealthcheckWaitStrategy forHealthcheck();
public static ShellStrategy forSuccessfulCommand(String command);
}
public interface WaitStrategy {
void waitUntilReady(WaitStrategyTarget target); // Throws: ContainerLaunchException
WaitStrategy withStartupTimeout(Duration timeout);
}Decision Guide - Which Wait Strategy to Use:
| Container Type | Recommended Strategy | Reason |
|---|---|---|
| Database (PostgreSQL, MySQL) | Wait.forListeningPort() | Fast, reliable port check |
| Web application | Wait.forHttp("/health").forStatusCode(200) | Ensures app is responding correctly |
| Message broker (Kafka, RabbitMQ) | Wait.forLogMessage(".*started.*", 1) | Waits for specific startup message |
| Image with HEALTHCHECK | Wait.forHealthcheck() | Uses Docker's built-in health check |
| Multiple conditions | new WaitAllStrategy().withStrategy(...) | Combines multiple checks |
Image pull policies and programmatic image building for Testcontainers. Supports remote Docker images, building from Dockerfiles, and Jib integration.
public class RemoteDockerImage extends LazyFuture<String> {
public RemoteDockerImage(DockerImageName imageName);
}
public class ImageFromDockerfile extends LazyFuture<String> {
public ImageFromDockerfile();
/**
* @param imageTag custom tag for the built image
* @param deleteOnExit true to delete image after JVM exits, false to keep it
*/
public ImageFromDockerfile(String imageTag, boolean deleteOnExit);
public ImageFromDockerfile withDockerfilePath(String path);
public ImageFromDockerfile withFileFromPath(String containerPath, Path hostPath);
public ImageFromDockerfile withBuildArg(String key, String value);
}
public interface ImagePullPolicy {
boolean shouldPull(DockerImageName imageName);
}
public class PullPolicy {
public static ImagePullPolicy defaultPolicy(); // Pulls if missing or untagged
public static ImagePullPolicy alwaysPull(); // Always pulls latest
public static ImagePullPolicy ageBased(Duration maxAge); // Pulls if older than maxAge
}Performance Tips:
postgres:15.2) instead of latest to enable cachingPullPolicy.defaultPolicy() for CI environments to avoid unnecessary pullsImageFromDockerfile with caching for faster test iterationswithReuse(true) for development (requires testcontainers.reuse.enable=true)Programmatic Docker image building with fluent Dockerfile construction. Build custom images from Dockerfiles with full control over build context, arguments, and layering.
/**
* Build images from Dockerfiles with full control.
*/
public class ImageFromDockerfile extends LazyFuture<String> {
public ImageFromDockerfile();
public ImageFromDockerfile(String imageTag, boolean deleteOnExit);
public ImageFromDockerfile withDockerfile(Path dockerfile);
public ImageFromDockerfile withDockerfileFromBuilder(Consumer<DockerfileBuilder> builderConsumer);
public ImageFromDockerfile withFileFromPath(String containerPath, Path hostPath);
public ImageFromDockerfile withFileFromClasspath(String containerPath, String resourcePath);
}
/**
* Fluent builder for constructing Dockerfiles programmatically.
*/
public class DockerfileBuilder {
public DockerfileBuilder from(String baseImage);
public DockerfileBuilder run(String... command);
public DockerfileBuilder cmd(String... command);
public DockerfileBuilder expose(int... ports);
public DockerfileBuilder env(String key, String value);
public DockerfileBuilder add(String source, String dest);
public DockerfileBuilder copy(String source, String dest);
public DockerfileBuilder workdir(String workdir);
public String build();
}Key Features:
Container startup validation strategies for one-shot and long-running containers. Different strategies for verifying that containers have started successfully and are ready for testing.
/**
* Base class for startup validation strategies.
*/
public abstract class StartupCheckStrategy {
public abstract StartupStatus checkStartupState(DockerClient dockerClient, String containerId);
public enum StartupStatus {
SUCCESSFUL, // Container started successfully
FAILED, // Container failed to start
NOT_YET_KNOWN // Status not yet determined
}
}
/**
* Common startup check implementations.
*/
public class IsRunningStartupCheckStrategy extends StartupCheckStrategy;
public class OneShotStartupCheckStrategy extends StartupCheckStrategy;
public class MinimumDurationRunningStartupCheckStrategy extends StartupCheckStrategy;When to Use:
Docker network creation and management for container communication. Networks enable containers to communicate using network aliases and service discovery.
public interface Network extends AutoCloseable {
static Network newNetwork();
static NetworkImpl.NetworkImplBuilder builder();
String getId();
void close();
Network SHARED; // Shared network instance (lifecycle managed by ResourceReaper, cannot be closed by user code)
}
public static class NetworkImpl implements Network {
/**
* Builder for creating custom Docker networks with advanced configuration.
* Generated by Lombok @Builder annotation.
*/
public static class NetworkImplBuilder {
/**
* Set the network driver (e.g., "bridge", "overlay", "host").
* Default: "bridge"
*/
public NetworkImplBuilder driver(String driver);
/**
* Enable or disable IPv6 networking.
* null = default (disabled), true = enabled, false = explicitly disabled
*/
public NetworkImplBuilder enableIpv6(Boolean ipv6);
/**
* Add a single modifier to customize the CreateNetworkCmd.
* Lombok @Singular generates both singular and plural methods.
*/
public NetworkImplBuilder createNetworkCmdModifier(Consumer<CreateNetworkCmd> modifier);
/**
* Add multiple modifiers to customize the CreateNetworkCmd.
*/
public NetworkImplBuilder createNetworkCmdModifiers(Collection<Consumer<CreateNetworkCmd>> modifiers);
/**
* Build the network. Network is created lazily on first getId() call.
*/
public NetworkImpl build();
}
}When to Use Networks:
Example:
try (Network network = Network.newNetwork()) {
GenericContainer<?> db = new GenericContainer<>(DockerImageName.parse("postgres:15"))
.withNetwork(network)
.withNetworkAliases("database"); // Other containers can reach at "database:5432"
GenericContainer<?> app = new GenericContainer<>(DockerImageName.parse("myapp:latest"))
.withNetwork(network)
.withEnv("DB_HOST", "database") // Use network alias
.dependsOn(db);
}Log and output stream management for containers. Provides multiple consumer implementations for capturing and processing container output.
public class OutputFrame {
public String getUtf8String();
public byte[] getBytes();
public OutputType getType();
public enum OutputType { STDOUT, STDERR, END }
// Static field for END frame marker
public static final OutputFrame END;
}
public class Slf4jLogConsumer extends BaseConsumer<Slf4jLogConsumer> {
public Slf4jLogConsumer(Logger logger);
}
public class ToStringConsumer extends BaseConsumer<ToStringConsumer> {
public String toUtf8String();
}
public class WaitingConsumer extends BaseConsumer<WaitingConsumer> {
public void waitUntil(Predicate<OutputFrame> predicate, long limit, TimeUnit limitUnit)
throws TimeoutException;
public void waitUntilEnd();
}Launch and manage multi-container Docker Compose environments for integration testing. Supports both legacy and modern Docker Compose APIs.
public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>>
implements Startable {
public SELF withExposedService(String serviceName, int servicePort);
public SELF withExposedService(String serviceName, int servicePort, WaitStrategy waitStrategy);
public SELF withScaledService(String serviceName, int instances);
public SELF withEnv(String key, String value);
public String getServiceHost(String serviceName, int servicePort);
public Integer getServicePort(String serviceName, int servicePort);
public void start(); // Throws: ContainerLaunchException
public void stop();
}Global configuration and customization of Testcontainers behavior. Configure Docker client strategies, image pull policies, timeout values, container reuse, and more through properties files, environment variables, or programmatic access.
import org.testcontainers.utility.TestcontainersConfiguration;
/**
* Singleton for accessing Testcontainers configuration.
* Configuration sources (highest to lowest precedence):
* 1. Environment variables (prefixed with TESTCONTAINERS_)
* 2. User properties (~/.testcontainers.properties)
* 3. Classpath properties (testcontainers.properties)
* 4. Default values
*/
public class TestcontainersConfiguration {
public static TestcontainersConfiguration getInstance();
public String getEnvVarOrProperty(String propertyName, String defaultValue);
public String getEnvVarOrUserProperty(String propertyName, String defaultValue);
public String getUserProperty(String propertyName, String defaultValue);
public boolean updateUserConfig(String prop, String value);
// Feature flags
public boolean isDisableChecks();
public boolean environmentSupportsReuse();
// Docker client configuration
public String getDockerClientStrategyClassName();
public String getTransportType();
// Image management
public String getImageSubstitutorClassName();
public String getImagePullPolicy();
public Integer getImagePullTimeout();
// Ryuk configuration
public boolean isRyukPrivileged();
public Integer getRyukTimeout();
}Common Configuration:
# ~/.testcontainers.properties
# Enable container reuse (development mode)
testcontainers.reuse.enable=true
# Docker client strategy
docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy
# Image pull policy
pull.policy=default
pull.timeout=300
# Ryuk (resource cleanup)
ryuk.container.privileged=false
ryuk.container.timeout=60Usage Example:
import org.testcontainers.utility.TestcontainersConfiguration;
TestcontainersConfiguration config = TestcontainersConfiguration.getInstance();
// Check if reuse is enabled
if (config.environmentSupportsReuse()) {
System.out.println("Container reuse is enabled");
}
// Update configuration
config.updateUserConfig("testcontainers.reuse.enable", "true");Beyond the core GenericContainer and DockerComposeContainer, Testcontainers provides specialized container implementations for advanced testing scenarios:
Container for running Docker MCP (Model Context Protocol) Gateway, enabling AI model integration and testing scenarios.
import org.testcontainers.containers.DockerMcpGatewayContainer;
import org.testcontainers.utility.DockerImageName;
/**
* Docker MCP Gateway container for AI model testing.
* Supported image: docker/mcp-gateway
* Exposed port: 8811
*/
public class DockerMcpGatewayContainer extends GenericContainer<DockerMcpGatewayContainer> {
public DockerMcpGatewayContainer(String dockerImageName);
public DockerMcpGatewayContainer(DockerImageName dockerImageName);
/**
* Add an MCP server with its tools.
* @param server the MCP server URL
* @param tools list of tool names to enable
* @return this container for method chaining
*/
public DockerMcpGatewayContainer withServer(String server, List<String> tools);
public DockerMcpGatewayContainer withServer(String server, String... tools);
/**
* Add secrets for MCP server authentication.
* @param secrets map of secret key-value pairs
* @return this container for method chaining
*/
public DockerMcpGatewayContainer withSecrets(Map<String, String> secrets);
public DockerMcpGatewayContainer withSecret(String secretKey, String secretValue);
/**
* Get the HTTP endpoint for the MCP Gateway.
* @return the gateway endpoint URL
*/
public String getEndpoint();
}Usage Example:
import org.testcontainers.containers.DockerMcpGatewayContainer;
import org.testcontainers.utility.DockerImageName;
import java.util.Map;
DockerMcpGatewayContainer gateway = new DockerMcpGatewayContainer(
DockerImageName.parse("docker/mcp-gateway:latest")
)
.withServer("https://mcp-server.example.com", "tool1", "tool2")
.withSecret("API_KEY", "your-api-key")
.withSecrets(Map.of(
"SECRET_1", "value1",
"SECRET_2", "value2"
));
gateway.start();
String endpoint = gateway.getEndpoint();
// Use endpoint to connect to MCP Gateway: http://host:8811Proxy container for Docker Model Runner service, enabling local AI model testing with OpenAI-compatible API.
import org.testcontainers.containers.DockerModelRunnerContainer;
import org.testcontainers.utility.DockerImageName;
/**
* Proxy container for Docker Model Runner (Docker Desktop).
* Extends SocatContainer to proxy to model-runner.docker.internal.
* Supported image: alpine/socat
* Exposed port: 80
*/
public class DockerModelRunnerContainer extends SocatContainer {
public DockerModelRunnerContainer(String image);
public DockerModelRunnerContainer(DockerImageName image);
/**
* Specify which AI model to pull and use.
* Model is pulled automatically after container starts.
* @param model model name (e.g., "llama3.2:1b", "phi3")
* @return this container for method chaining
*/
public DockerModelRunnerContainer withModel(String model);
/**
* Get the base endpoint for model runner API.
* @return the base URL
*/
public String getBaseEndpoint();
/**
* Get the OpenAI-compatible endpoint.
* @return the OpenAI API endpoint URL
*/
public String getOpenAIEndpoint();
}Usage Example:
import org.testcontainers.containers.DockerModelRunnerContainer;
import org.testcontainers.utility.DockerImageName;
// Create model runner proxy for local AI testing
DockerModelRunnerContainer modelRunner = new DockerModelRunnerContainer(
DockerImageName.parse("alpine/socat:latest")
)
.withModel("llama3.2:1b"); // Automatically pulls model on startup
modelRunner.start();
String baseUrl = modelRunner.getBaseEndpoint();
String openAIUrl = modelRunner.getOpenAIEndpoint();
// Use OpenAI-compatible API
// POST to openAIUrl + "/chat/completions"Network proxy/relay container for advanced networking scenarios and port forwarding.
import org.testcontainers.containers.SocatContainer;
/**
* Socat container for network proxying and port forwarding.
* Useful for connecting containers to external services.
*/
public class SocatContainer extends GenericContainer<SocatContainer> {
public SocatContainer withTarget(int port, String target);
}Captures screen recordings during test execution for GUI testing and debugging.
import org.testcontainers.containers.VncRecordingContainer;
/**
* VNC recording container for capturing GUI test sessions.
* Useful for debugging Selenium tests and visual regression testing.
*/
public class VncRecordingContainer extends GenericContainer<VncRecordingContainer> {
// Start/stop recording methods
}Singleton enum for port forwarding between containers and host. Used internally by Testcontainers.exposeHostPorts().
Note: These specialized containers are available in the core Testcontainers library. For database-specific containers (MySQL, PostgreSQL, MongoDB, etc.), check the Testcontainers modules documentation for dedicated module implementations with database-specific APIs.
Docker client factory and provider strategies for connecting to Docker hosts. Automatically detects and configures the appropriate Docker client based on the environment.
public class DockerClientFactory {
public static DockerClientFactory instance();
public DockerClient client();
public boolean isDockerAvailable();
public String dockerHostIpAddress();
public String getActiveApiVersion();
public static final String TESTCONTAINERS_LABEL;
public static final String SESSION_ID;
}Troubleshooting:
isDockerAvailable() returns false, ensure Docker daemon is running~/.testcontainers.properties for custom Docker host configurationDOCKER_HOST environment variable if using remote DockerEssential utility classes for working with Docker images, files, and configuration. These classes provide type-safe abstractions for common Docker operations.
public class DockerImageName {
public static DockerImageName parse(String fullImageName); // Throws: IllegalArgumentException
public DockerImageName withTag(String tag);
public DockerImageName withRegistry(String registry);
public String asCanonicalNameString();
public boolean isCompatibleWith(DockerImageName other);
}
public class MountableFile implements Transferable {
public static MountableFile forHostPath(String path);
public static MountableFile forHostPath(Path path);
public static MountableFile forClasspathResource(String resourcePath);
public String getResolvedPath();
}
// Import: org.testcontainers.images.builder.Transferable
public interface Transferable {
static Transferable of(byte[] bytes);
static Transferable of(byte[], int fileMode);
static Transferable of(String content);
static Transferable of(String content, int fileMode);
void transferTo(TarArchiveOutputStream outputStream, String destinationPath);
}public interface ContainerState {
boolean isRunning();
boolean isCreated();
boolean isHealthy();
String getHost();
String getContainerId();
Integer getMappedPort(int originalPort); // Throws: IllegalArgumentException if port not exposed
Integer getFirstMappedPort();
List<Integer> getExposedPorts();
String getLogs();
String getLogs(OutputFrame.OutputType... types);
Container.ExecResult execInContainer(String... command) throws Exception;
void copyFileToContainer(Transferable transferable, String containerPath);
void copyFileFromContainer(String containerPath, String destinationPath);
}Result of executing a command in a container. Nested class within Container interface.
/**
* Result of executing a command inside a container.
* Nested class: Container.ExecResult
*/
public static class Container.ExecResult {
public String getStdout();
public String getStderr();
public int getExitCode();
}public interface Startable extends AutoCloseable {
void start(); // Throws: ContainerLaunchException
void stop();
default Set<Startable> getDependencies() {
return Collections.emptySet(); // Default: no dependencies
}
void close(); // Alias for stop()
}public enum BindMode {
READ_ONLY,
READ_WRITE
}
/**
* IP protocols supported by Docker for port bindings.
* Used when specifying port protocol in container configuration.
*/
public enum InternetProtocol {
TCP, // Transmission Control Protocol
UDP // User Datagram Protocol
}
/**
* SELinux context modes for volume binds.
* Used with bind mounts to control SELinux labeling.
*/
public enum SelinuxContext {
SHARED, // :z - Content is shared among multiple containers
SINGLE, // :Z - Content is private and unshared (relabeled for single container)
NONE // No SELinux labeling
}/**
* Marks that the annotated API is subject to change and should not be
* considered a stable API. Methods, classes, or fields marked with this
* annotation may change in future versions without following semantic
* versioning guarantees.
*
* When using APIs marked with @UnstableAPI, be prepared to adapt your
* code during library upgrades.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Documented
public @interface UnstableAPI {
}Common exceptions thrown by Testcontainers operations.
/**
* Exception thrown when a container fails to start or become ready.
* This is the primary exception for container lifecycle failures.
*/
public class ContainerLaunchException extends RuntimeException {
public ContainerLaunchException(String message);
public ContainerLaunchException(String message, Throwable cause);
}
/**
* Exception thrown when Docker image fetch/pull operations fail.
* Typically indicates network issues or invalid image names.
*/
public class ContainerFetchException extends RuntimeException {
public ContainerFetchException(String message);
public ContainerFetchException(String message, Throwable cause);
}Common Causes and Solutions:
ContainerLaunchException:
Cause: Wait strategy timeout - container started but didn't become ready within timeout
container.withStartupTimeout(Duration.ofMinutes(2))Cause: Container crashed immediately after starting
container.getLogs(), verify command and configurationCause: Port not exposed before calling getMappedPort()
.withExposedPorts(port) before starting containerCause: Invalid wait strategy configuration
ContainerFetchException:
Cause: Image not found in registry
Cause: Network connectivity issues
Cause: Authentication required for private registry
Exception Handling Example:
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.ContainerFetchException;
try {
GenericContainer<?> container = new GenericContainer<>("myapp:latest")
.withExposedPorts(8080)
.waitingFor(Wait.forHttp("/health").forStatusCode(200));
container.start();
} catch (ContainerFetchException e) {
// Image pull failed
System.err.println("Failed to fetch image: " + e.getMessage());
// Check image name, registry availability, credentials
} catch (ContainerLaunchException e) {
// Container failed to start or become ready
System.err.println("Container failed to launch: " + e.getMessage());
if (e.getCause() instanceof TimeoutException) {
// Wait strategy timeout
System.err.println("Container didn't become ready in time");
System.err.println("Logs: " + container.getLogs());
} else {
// Other launch failure
System.err.println("Check container configuration and logs");
}
}@Test
public void testWithDatabase() {
try (GenericContainer<?> postgres = new GenericContainer<>(DockerImageName.parse("postgres:15"))
.withExposedPorts(5432)
.withEnv("POSTGRES_PASSWORD", "test")
.withEnv("POSTGRES_DB", "testdb")
.waitingFor(Wait.forListeningPort())
.withStartupTimeout(Duration.ofSeconds(60))) {
postgres.start();
String jdbcUrl = String.format("jdbc:postgresql://%s:%d/testdb",
postgres.getHost(), postgres.getMappedPort(5432));
// Run database tests
}
}@Test
public void testMultiContainer() {
try (Network network = Network.newNetwork()) {
GenericContainer<?> db = new GenericContainer<>(DockerImageName.parse("postgres:15"))
.withNetwork(network)
.withNetworkAliases("db")
.withEnv("POSTGRES_PASSWORD", "secret")
.withExposedPorts(5432)
.waitingFor(Wait.forListeningPort());
GenericContainer<?> app = new GenericContainer<>(DockerImageName.parse("myapp:latest"))
.withNetwork(network)
.withEnv("DB_URL", "jdbc:postgresql://db:5432/postgres")
.withExposedPorts(8080)
.dependsOn(db)
.waitingFor(Wait.forHttp("/health").forStatusCode(200));
app.start(); // Starts db first due to dependsOn()
String appUrl = String.format("http://%s:%d",
app.getHost(), app.getMappedPort(8080));
// Run tests
}
}@Test
public void testWithErrorHandling() {
GenericContainer<?> container = null;
try {
container = new GenericContainer<>(DockerImageName.parse("myapp:latest"))
.withExposedPorts(8080)
.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("container")))
.waitingFor(Wait.forHttp("/health").forStatusCode(200))
.withStartupTimeout(Duration.ofMinutes(2));
container.start();
// Run tests
} catch (ContainerLaunchException e) {
if (container != null) {
System.err.println("Container failed to start. Logs:");
System.err.println(container.getLogs());
}
throw e;
} catch (TimeoutException e) {
System.err.println("Container startup timed out. Check wait strategy.");
throw e;
} finally {
if (container != null) {
container.stop();
}
}
}| Issue | Cause | Solution |
|---|---|---|
ContainerLaunchException | Container failed to start | Check container.getLogs() for error messages |
TimeoutException | Container didn't become ready | Increase timeout or adjust wait strategy |
IllegalArgumentException on getMappedPort() | Port not exposed | Call .withExposedPorts() before starting |
| Port conflict | Container using fixed port already in use | Use dynamic ports (default) instead of withFixedExposedPort() |
| Image pull fails | Network issue or invalid image name | Check Docker connectivity and image name |
| Container stops immediately | Incorrect command or missing dependencies | Check logs and ensure image is configured correctly |
Enable debug logging to troubleshoot issues:
# In ~/.testcontainers.properties
testcontainers.diagnostics.enabled=trueOr programmatically:
// Capture all container output
container.withLogConsumer(new Slf4jLogConsumer(logger).withSeparateOutputStreams());latest to enable Docker layer cachingtestcontainers.reuse.enable=true + .withReuse(true))PullPolicy.defaultPolicy() to cache pulled images