Spring Boot
Embedded Postgres
Testing
Java
Database Integration

Embedded Postgres for Spring Boot Tests

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

If your Spring Boot tests need real PostgreSQL behavior, an embedded Postgres instance is often a better fit than H2 compatibility mode. It gives you PostgreSQL-specific SQL behavior, types, and migration realism without depending on a shared external database. The main decision is whether embedded Postgres is the best tradeoff for your environment or whether Testcontainers is a better modern default.

Why Use Embedded Postgres At All

The usual reason is test fidelity. H2 is fast, but it does not behave exactly like PostgreSQL, so database-related bugs can slip through. Embedded Postgres narrows that gap by running a real PostgreSQL engine locally during tests.

That makes it useful for:

  • repository integration tests
  • migration verification
  • SQL behavior that depends on PostgreSQL semantics
  • CI environments where Docker is unavailable or undesirable

If Docker is easy to use in your team, Testcontainers is often the stronger choice today. But embedded Postgres remains useful when you want a lighter local dependency story.

A Typical Setup Pattern

One common pattern is to start the embedded database once for the test class and feed its JDBC settings into Spring Boot dynamically.

java
1import io.zonky.test.db.postgres.embedded.EmbeddedPostgres;
2import org.junit.jupiter.api.AfterAll;
3import org.junit.jupiter.api.BeforeAll;
4import org.springframework.test.context.DynamicPropertyRegistry;
5import org.springframework.test.context.DynamicPropertySource;
6
7public class PostgresTestBase {
8    private static EmbeddedPostgres postgres;
9
10    @BeforeAll
11    static void startPostgres() throws Exception {
12        postgres = EmbeddedPostgres.start();
13    }
14
15    @AfterAll
16    static void stopPostgres() throws Exception {
17        postgres.close();
18    }
19
20    @DynamicPropertySource
21    static void registerProps(DynamicPropertyRegistry registry) {
22        registry.add("spring.datasource.url", () -> postgres.getJdbcUrl("postgres", "postgres"));
23        registry.add("spring.datasource.username", () -> "postgres");
24        registry.add("spring.datasource.password", () -> "postgres");
25    }
26}

Then a Spring Boot test can inherit from that base and use the real PostgreSQL-backed DataSource.

Example Repository Test

java
1import org.junit.jupiter.api.Test;
2import org.springframework.beans.factory.annotation.Autowired;
3import org.springframework.boot.test.context.SpringBootTest;
4
5@SpringBootTest
6class UserRepositoryTest extends PostgresTestBase {
7
8    @Autowired
9    private UserRepository userRepository;
10
11    @Test
12    void savesUser() {
13        User user = new User();
14        user.setEmail("[email protected]");
15        userRepository.save(user);
16    }
17}

This kind of test is much closer to production behavior than an H2-backed substitute.

Schema Management Still Matters

Embedded Postgres gives you a PostgreSQL engine, but it does not remove the need for schema management discipline. You should still run the same Flyway or Liquibase migrations that production uses.

That way your test database becomes a real compatibility check for the schema, not just a convenient place to persist rows temporarily.

Embedded Postgres Versus Testcontainers

A practical comparison looks like this:

  • embedded Postgres: simpler local dependency story, no Docker required
  • Testcontainers: closer to production packaging, easier version pinning, broader ecosystem support

If your team already uses Docker heavily in CI, Testcontainers is often the more future-proof path. If Docker availability is inconsistent or restricted, embedded Postgres can still be the easier solution.

Test Scope Matters

Do not run a full embedded Postgres-backed context for every tiny unit test. Use it for integration tests where the database behavior is actually part of what you are verifying. Pure business-logic tests should still run without Spring or a database whenever possible.

That separation keeps the suite fast and makes failures easier to localize.

Common Pitfalls

  • Using embedded Postgres for every test in the project, including tests that do not care about persistence behavior.
  • Forgetting to run real migrations and therefore missing schema drift problems.
  • Treating embedded Postgres as identical to production while ignoring version mismatches.
  • Choosing H2 for PostgreSQL-specific features and then discovering SQL differences too late.
  • Ignoring Testcontainers as an alternative when Docker-based infrastructure is already standard in the team.

Summary

  • Embedded Postgres is useful when tests need real PostgreSQL behavior without a shared external database.
  • It is a stronger fit than H2 when PostgreSQL-specific SQL and migrations matter.
  • Feed the embedded database connection details into Spring Boot dynamically during tests.
  • Run the same migrations you use in production so tests verify real schema behavior.
  • Prefer embedded Postgres or Testcontainers intentionally based on your team's environment and tooling constraints.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.