Skip to content
Spring Boot sb testing 3 min read

Testcontainers

Testcontainers runs real infrastructure — Postgres, MySQL, MongoDB, Kafka, Redis — inside throwaway Docker containers for the duration of your tests. Instead of testing against an embedded H2 that behaves subtly differently from production, you test against the actual engine you ship with. Spring Boot 3.1+ integrates it tightly via @ServiceConnection, which wires Spring’s connection properties to the container with zero configuration.

Dependencies

Add the Testcontainers BOM and the modules you need. The Spring Boot starter adds @ServiceConnection support.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>

Note: Testcontainers needs a running Docker (or compatible) daemon on the machine and CI runner. If Docker is not available the tests fail fast at startup.

@Testcontainers and @Container

@Testcontainers activates the JUnit 5 extension that manages container lifecycle. @Container marks the container field. A static container starts once for the whole test class; a non-static one restarts per test method.

@SpringBootTest
@Testcontainers
class ProductRepositoryContainerTest {

    @Container
    static PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>("postgres:16-alpine");

    @Autowired ProductRepository repository;
}

Wiring the datasource

Without @ServiceConnection you would copy the container’s JDBC URL, username, and password into Spring properties by hand using @DynamicPropertySource:

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

@ServiceConnection — the easy way

Since Spring Boot 3.1, adding @ServiceConnection to the container makes Spring derive all the connection details automatically. The boilerplate above disappears entirely.

@SpringBootTest
@Testcontainers
class ProductRepositoryContainerTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>("postgres:16-alpine");

    @Autowired ProductRepository repository;

    @Test
    void savesAndReadsBack() {
        Product saved = repository.save(new Product("Keyboard", new BigDecimal("49.90")));
        assertThat(repository.findById(saved.getId())).isPresent();
    }
}

Output:

Creating container for image: postgres:16-alpine
Container postgres:16-alpine started in PT2.41S
ProductRepositoryContainerTest > savesAndReadsBack() PASSED

@ServiceConnection understands many container types out of the box, so the same pattern works for MongoDB, Redis, Kafka, and more.

ContainerSpring connection it configures
PostgreSQLContainer / MySQLContainerJDBC DataSource
MongoDBContainerspring.data.mongodb.*
RedisContainer (community)Redis connection
KafkaContainerspring.kafka.bootstrap-servers

MongoDB example

The pattern is identical for a document database.

@DataMongoTest
@Testcontainers
class CustomerMongoTest {

    @Container
    @ServiceConnection
    static MongoDBContainer mongo = new MongoDBContainer("mongo:7");

    @Autowired CustomerRepository repository;

    @Test
    void storesDocument() {
        repository.save(new Customer("[email protected]"));
        assertThat(repository.findByEmail("[email protected]")).isPresent();
    }
}

Reusing containers across classes

Starting a container per class is fine for a few tests but adds up. There are two ways to share one container across many test classes.

1. A shared base class with a static container that all integration tests extend — JUnit starts the static container once and the cached Spring context reuses it.

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Testcontainers
abstract class AbstractIntegrationTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
}

2. Reusable containers that survive across separate JVM runs locally. Call .withReuse(true) and enable reuse in ~/.testcontainers.properties.

@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16-alpine").withReuse(true);
# ~/.testcontainers.properties
testcontainers.reuse.enable=true

Warning: Reusable containers are not torn down automatically — they persist between runs for speed. Never enable reuse in CI, where you want a clean, ephemeral environment every build.

Tip: Pin image tags (postgres:16-alpine, not postgres:latest) so tests are reproducible and do not silently change when a new image is published.

Testcontainers at development time

Spring Boot also lets you run the application (not just tests) against Testcontainers during development via a @TestConfiguration and the spring-boot-testcontainers starter — handy when you want ./mvnw spring-boot:test-run to start a real Postgres locally with no manual setup.

Last updated June 13, 2026
Was this helpful?