Home/Learn/Spring Boot/Testcontainers with Spring Boot

Testcontainers with Spring Boot

Advanced
Testing

Testcontainers spin up real Docker containers (Postgres, Kafka, Redis) for integration tests; the @Testcontainers + @Container annotations manage lifecycle.

Overview

Testcontainers is a Java library that spins up real Docker containers during tests, giving you an actual Postgres, Kafka, Redis, or any other service instead of in-memory fakes. This eliminates the "works on my machine" class of bugs caused by differences between H2 and real Postgres, or between embedded brokers and real Kafka. Spring Boot 3.1+ has first-class Testcontainers support via `@ServiceConnection` — it auto-configures the datasource/broker URL from the container's mapped port with zero boilerplate. Containers are started once per test class (or shared with `@Container` as a static field) and stopped after. The `spring-boot-testcontainers` module provides the `@ServiceConnection` annotation and Spring Boot's `@ImportTestcontainers` for reuse.

Basic Setup with @ServiceConnection (Spring Boot 3.1+)

`@ServiceConnection` is the zero-boilerplate way: it reads the container's mapped port and overrides the relevant auto-configuration properties (datasource URL, Kafka bootstrap-servers, Redis host/port) automatically. No `@DynamicPropertySource` required.

Spring Boot 3.1 — @ServiceConnection with Postgres + Kafka
@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {

    @Container
    @ServiceConnection               // auto-configures spring.datasource.url from the container
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16");

    @Container
    @ServiceConnection               // auto-configures spring.kafka.bootstrap-servers
    static KafkaContainer kafka =
        new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.6.0"));

    @Autowired OrderService orderService;
    @Autowired OrderRepository orderRepo;

    @Test
    void shouldPersistOrder() {
        Order saved = orderService.place(new CreateOrderRequest("SKU-1", 2));
        assertThat(orderRepo.findById(saved.getId())).isPresent();
    }
}

Reusable Containers with @ImportTestcontainers

Declare containers in a configuration class and annotate tests with `@ImportTestcontainers` so all tests share the same containers (started once per JVM run). This dramatically speeds up test suites by avoiding repeated container start/stop. Combine with `@ActiveProfiles("test")` to keep integration tests separate from unit tests.

Spring Boot — shared containers with @ImportTestcontainers
// Shared container configuration
class TestContainersConfig {

    @Bean
    @ServiceConnection
    PostgreSQLContainer<?> postgresContainer() {
        return new PostgreSQLContainer<>("postgres:16")
            .withInitScript("schema.sql");   // run DDL on startup
    }

    @Bean
    @ServiceConnection
    RedisContainer redisContainer() {
        return new RedisContainer(RedisContainer.DEFAULT_IMAGE_NAME.withTag("7"));
    }
}

// Import in tests — containers are shared across all annotated tests
@SpringBootTest
@ImportTestcontainers(TestContainersConfig.class)
class UserServiceIntegrationTest {
    @Autowired UserService userService;

    @Test
    void shouldCacheUser() {
        userService.findById(1L);  // first call — DB hit
        userService.findById(1L);  // second call — Redis cache hit
    }
}

Dynamic Properties (Pre-3.1) and Custom Init Scripts

Before Spring Boot 3.1, use `@DynamicPropertySource` to register container URLs into the Spring environment. For databases, run migration scripts on the container using `withInitScript()` or `withCopyFileToContainer()`. Testcontainers reuses containers between runs if `TESTCONTAINERS_REUSE_ENABLE=true` is set in `~/.testcontainers.properties` — eliminates startup time during local TDD loops.

Testcontainers — @DynamicPropertySource (pre-3.1) and reuse
// Pre-3.1: @DynamicPropertySource
@SpringBootTest
@Testcontainers
class LegacyIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test")
        .withInitScript("db/schema.sql");    // classpath resource

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

# ~/.testcontainers.properties — enable container reuse in local dev
testcontainers.reuse.enable=true

Key Points to Remember

  • 1Testcontainers starts real Docker containers for integration tests — no fake in-memory DBs
  • 2@ServiceConnection (Boot 3.1+) auto-configures datasource URL / broker address from the container
  • 3Declare containers as static fields so they start once per test class, not per test method
  • 4@ImportTestcontainers shares a container configuration class across multiple test classes
  • 5TESTCONTAINERS_REUSE_ENABLE=true skips restart between runs — speeds up local TDD
  • 6Use @DynamicPropertySource for pre-3.1 Spring Boot to feed container URLs to the context

Interview Questions

Sign in to ask Aria
1

Why would you use Testcontainers instead of an in-memory H2 database for integration tests?

EasyThoughtWorks
2

What does @ServiceConnection do in Spring Boot 3.1 and what did you need before it?

MediumPivotal
3

How would you share a single Postgres container across multiple test classes to speed up your suite?

MediumAmazon
4

What is the risk of using H2 compatibility mode instead of Testcontainers for Postgres?

MediumBooking.com
5

How does TESTCONTAINERS_REUSE_ENABLE=true work and when should you not use it?

HardRed Hat

Ask Aria about Testcontainers with Spring Boot

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…