This document covers controlling MySQL container startup, shutdown, and monitoring container state.
Lifecycle Methods:
start(): void - Starts container and blocks until ready (throws ContainerLaunchException or IllegalStateException)stop(): void - Stops and removes containerisRunning(): boolean - Returns boolean indicating if container is runningConfiguration:
withReuse(boolean reuse): SELF - Enable container reuse (requires testcontainers.reuse.enable=true in ~/.testcontainers.properties)AutoCloseable Support:
MySQLContainer implements AutoCloseable - Use with try-with-resources for automatic cleanupState Transitions:
start(): isRunning() returns falsestart() completes: isRunning() returns truestop(): isRunning() returns falsestart(): isRunning() returns falseStart the MySQL container and wait until it's ready to accept connections.
/**
* Starts the MySQL container.
* This method pulls the Docker image if not already present, creates the container,
* starts it, and waits until the database is ready to accept connections.
*
* The method blocks until the container is fully started and the test query succeeds,
* or until the startup timeout is reached.
*
* This method can only be called once per container instance. Calling it multiple
* times will throw an IllegalStateException.
*
* All configuration methods must be called before this method.
*
* @throws IllegalStateException if the container cannot be started or accessed within the timeout,
* or if start() is called multiple times, or if configuration is invalid
* @throws ContainerLaunchException if the container fails to start due to configuration issues
* (e.g., empty password with non-root user, invalid Docker image, insufficient resources)
*/
void start();Usage Example:
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"));
mysql.start(); // Blocks until container is ready
// Container is now running and ready for connections
String jdbcUrl = mysql.getJdbcUrl();Startup Process:
Startup Timeout:
withStartupTimeoutSeconds(int)IllegalStateException if timeout exceededStartup Failure Scenarios:
Stop and remove the MySQL container.
/**
* Stops the MySQL container.
* This method stops the running container and removes it, including any volumes.
* Any data in the container is lost unless persisted externally.
*
* This method is idempotent: calling it multiple times or on an already-stopped
* container has no effect and does not throw exceptions.
*
* After calling stop(), the container cannot be restarted. Create a new instance
* if you need another container.
*/
void stop();Usage Example:
MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"));
mysql.start();
// Use container...
mysql.stop(); // Stops and removes the containerStop Behavior:
Check whether the container is currently running.
/**
* Checks if the container is currently running.
* Returns true only if the container has been successfully started and has not
* been stopped. Returns false before start() is called, after stop() is called,
* or if start() failed with an exception.
*
* @return true if the container is running, false otherwise
*/
boolean isRunning();Usage Example:
MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"));
boolean running1 = mysql.isRunning(); // false (not started yet)
mysql.start();
boolean running2 = mysql.isRunning(); // true (running)
mysql.stop();
boolean running3 = mysql.isRunning(); // false (stopped)State Transitions:
start(): isRunning() returns falsestart() completes: isRunning() returns truestop(): isRunning() returns falsestart(): isRunning() returns falseState Checking Pattern:
if (mysql.isRunning()) {
// Safe to use container
String jdbcUrl = mysql.getJdbcUrl();
} else {
// Container not ready, must call start() first
mysql.start();
}Enable container reuse across multiple test runs to improve performance.
/**
* Enables or disables container reuse.
* When enabled, the container will not be stopped and removed after the test completes.
* Subsequent test runs can reuse the same container instance, significantly improving startup time.
*
* Requires Testcontainers reuse feature to be enabled in ~/.testcontainers.properties:
* testcontainers.reuse.enable=true
*
* If reuse is enabled but the property file is not configured, the container will still
* be stopped and removed after tests complete.
*
* @param reuse true to enable reuse, false to disable (default: false)
* @return self for method chaining (SELF extends MySQLContainer<SELF>)
*/
SELF withReuse(boolean reuse);Usage Example:
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
.withDatabaseName("testdb")
.withReuse(true); // Enable reuse
mysql.start();
// First run: container is created and started
// Subsequent runs: existing container is reused if still runningReuse Configuration:
To enable container reuse, add this line to ~/.testcontainers.properties:
testcontainers.reuse.enable=trueReuse Behavior:
docker ps -a --filter label=org.testcontainers.reuse.enable=trueWhen to Use Reuse:
When NOT to Use Reuse:
The recommended way to manage container lifecycle is using try-with-resources, which ensures proper cleanup.
// MySQLContainer implements AutoCloseable
// close() method is called automatically when exiting try blockUsage Example:
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
import java.sql.Connection;
import java.sql.SQLException;
public void testWithAutoClose() throws SQLException {
try (MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))) {
mysql.start();
// Use container
try (Connection conn = mysql.createConnection("")) {
// Execute queries...
}
// Container is automatically stopped and removed when exiting try block
}
}Benefits:
stop()Exception Handling:
try (MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))) {
mysql.start();
// If exception occurs here, container is still cleaned up
throw new RuntimeException("Test failure");
} // Container is stopped here even if exception was thrownimport org.junit.jupiter.api.Test;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
@Testcontainers
public class MySQLJUnit5Test {
@Container
private static MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
.withDatabaseName("testdb");
@Test
public void testDatabase() {
// Container is automatically started before tests and stopped after
String jdbcUrl = mysql.getJdbcUrl();
// Run test...
}
}JUnit 5 Behavior:
@Container with static field: Container started once before all tests, stopped after all tests@Container with instance field: Container started before each test, stopped after each test@Testcontainers annotation enables automatic lifecycle managementstart() or stop() manuallyimport org.junit.ClassRule;
import org.junit.Test;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
public class MySQLJUnit4Test {
@ClassRule
public static MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
.withDatabaseName("testdb");
@Test
public void testDatabase() {
// Container is automatically started before tests and stopped after
String jdbcUrl = mysql.getJdbcUrl();
// Run test...
}
}JUnit 4 Behavior:
@ClassRule: Container started once before all tests, stopped after all tests@Rule: Container started before each test, stopped after each teststart() or stop() manuallyFor more control over container lifecycle:
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
public class ManualLifecycleTest {
private MySQLContainer<?> mysql;
public void setUp() {
mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
.withDatabaseName("testdb");
mysql.start();
}
public void tearDown() {
if (mysql != null && mysql.isRunning()) {
mysql.stop();
}
}
public void runTest() {
// Use container
String jdbcUrl = mysql.getJdbcUrl();
}
}Manual Management Notes:
start() and stop()isRunning() before calling stop() to avoid errorsShare a single container across multiple test classes to improve performance:
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
public abstract class AbstractMySQLTest {
protected static final MySQLContainer<?> MYSQL_CONTAINER;
static {
MYSQL_CONTAINER = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
.withDatabaseName("testdb")
.withReuse(true);
MYSQL_CONTAINER.start();
}
protected String getJdbcUrl() {
return MYSQL_CONTAINER.getJdbcUrl();
}
protected String getUsername() {
return MYSQL_CONTAINER.getUsername();
}
protected String getPassword() {
return MYSQL_CONTAINER.getPassword();
}
}
// Test classes extend AbstractMySQLTest
public class UserRepositoryTest extends AbstractMySQLTest {
// Tests use shared container via getJdbcUrl(), etc.
}
public class OrderRepositoryTest extends AbstractMySQLTest {
// Tests use shared container via getJdbcUrl(), etc.
}Shared Container Considerations:
withReuse(true) for persistence across JVM restartsConfigure how long to wait for the container to become ready:
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
.withStartupTimeoutSeconds(180); // Wait up to 3 minutes (default: 120)
mysql.start();Timeout Scenarios:
isRunning() before using container if lifecycle is managed manuallyIllegalStateException: Thrown when the container starts but cannot be accessed via JDBC within the timeout period. This typically indicates:
Solution:
// Increase timeout
mysql.withStartupTimeoutSeconds(300)
.withConnectTimeoutSeconds(180);
// Enable logging to diagnose
mysql.withLogConsumer(new Slf4jLogConsumer(logger));ContainerLaunchException: Thrown when the container fails to start due to configuration issues, such as:
Solution:
// Check configuration
mysql.withUsername("root")
.withPassword("")
.withEnv("MYSQL_ROOT_HOST", "%"); // Required for root with empty password
// Verify Docker resources
// Check Docker logs: docker logs <container-id>To diagnose startup issues, enable container log output:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.utility.DockerImageName;
Logger logger = LoggerFactory.getLogger(getClass());
MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
.withLogConsumer(new Slf4jLogConsumer(logger));
mysql.start();
// Container logs will be output via SLF4J