skip to content

With @DataJpaTest on an embedded DB, how is the schema created, and how do Hibernate ddl-auto vs Flyway/Liquibase interact?

level: seniorimportance: should knowfreq 45%

answer

  1. Embedded default ddl-auto=create-drop
  2. Flyway/Liquibase on classpath → migrations build schema
  3. Migration present → Hibernate ddl-auto=none
  4. H2 chokes on vendor DDL → Testcontainers
  5. ddl-auto=validate to catch entity/schema drift

basics

~10 s

By default the embedded DB gets its schema from Hibernate ddl-auto=create-drop (generated from your @Entity mappings). If Flyway or Liquibase is on the classpath, Spring Boot runs those migrations instead to build the schema.

solid answer

~40 s

On an embedded database, @DataJpaTest defaults `spring.jpa.hibernate.ddl-auto` to `create-drop`, so Hibernate generates the schema from your entity mappings and drops it afterward — convenient but low-fidelity because it can hide missing or wrong migrations. If Flyway or Liquibase is on the classpath, Spring Boot enables them and they build the schema from your migration scripts, and Hibernate then defaults to `none`. That's higher fidelity since tests exercise the same DDL as production. Two big gotchas: (1) vendor-specific migration DDL may fail on H2, pushing you toward Testcontainers with a real DB; (2) if you rely on ddl-auto in tests but migrations in production, the two schemas can silently diverge. Best practice is to run the real migrations in tests against a production-matching database.

code

java · 20 lines
java
// Run the REAL Liquibase/Flyway migrations against a real Postgres,
// then have Hibernate VALIDATE that entities match the migrated schema.
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=validate")
@Testcontainers
class SchemaFidelityIT {

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

    @Autowired CustomerRepository customers;

    @Test
    void schemaMatchesEntities() {
        // If a migration is missing a column an @Entity maps,
        // ddl-auto=validate fails context startup before this runs.
        assertThat(customers.count()).isZero();
    }
}

go deeper

for a junior

Know the schema is auto-created for the embedded DB so tests have tables to use.

for a middle

Explain create-drop vs migration tools and that Flyway/Liquibase run automatically if present.

for a senior

Detail the precedence (migrations → ddl-auto=none), the validate safety net, and H2-vs-vendor DDL fidelity risks.

for a principal

Set policy: exercise real migrations in tests against production-matching DBs to prevent schema drift and vendor-DDL surprises.

## Where the schema comes from A `@DataJpaTest` needs a schema in its (by default embedded) database. There are two mutually-relevant sources: ### 1. Hibernate DDL generation (`spring.jpa.hibernate.ddl-auto`) When using an **embedded** database, Spring Boot sets `ddl-auto` to **`create-drop`** by default: Hibernate reads your `@Entity`/`@Table`/`@Column` mappings and emits `CREATE TABLE ...` at startup, then drops everything at shutdown. Values: - `none` — do nothing. - `validate` — check entities match an existing schema, fail if not. - `update` — additively alter the schema (never in production). - `create` — drop+create at startup. - `create-drop` — create at startup, drop at shutdown (embedded default). This is fast and zero-config but the schema is *derived from entities*, so it can't catch a mismatch between your entities and your real migrations. ### 2. Migration tools (Flyway / Liquibase) If `flyway-core` or `liquibase-core` is on the classpath, Spring Boot **auto-runs** them on the DataSource, including inside `@DataJpaTest`. They apply your versioned scripts (`V1__init.sql`, changelogs) to build the schema. When a migration tool is active, Hibernate's `ddl-auto` effectively defaults to **`none`** so the two don't fight. This gives **higher fidelity**: tests run the same DDL that production will. ### `@AutoConfigureTestDatabase` also has `initialize-schema` for `schema.sql`/`data.sql` init, but Flyway/Liquibase take precedence when present. ## Interaction / precedence summary - **No migration tool** → Hibernate `create-drop` generates schema from entities. - **Flyway/Liquibase present** → migrations build schema, Hibernate `ddl-auto=none`. - You can force validation with `spring.jpa.hibernate.ddl-auto=validate` to assert entities match the migrated schema — a useful safety net. ## Gotchas 1. **H2 vs vendor DDL**: Flyway/Liquibase scripts written for Postgres (`SERIAL`, `JSONB`, `GENERATED ALWAYS AS IDENTITY`, `ON CONFLICT`) may fail on H2. H2 has compatibility modes but they're imperfect. This is a top reason to switch to `Replace.NONE` + Testcontainers. 2. **Schema drift**: relying on `create-drop` (entity-derived) in tests while production uses migrations means a broken/missing migration passes tests. Prefer running real migrations in tests, or at least `ddl-auto=validate`. 3. **Ordering**: Flyway/Liquibase run before Hibernate validates; if a migration is missing a column an entity expects, `validate` catches it. 4. **Test data**: `data.sql` / `@Sql` scripts run to seed data; be mindful they run within the per-test transaction context. ## When to choose what - Fast, low-fidelity, no migrations yet → embedded + `create-drop`. - You have migrations and want them exercised → keep Flyway/Liquibase and, for full fidelity, run against Testcontainers so vendor DDL is validated on the real engine.

  • With Flyway on the classpath, what does spring.jpa.hibernate.ddl-auto default to in a @DataJpaTest?
    Effectively 'none' — Spring Boot lets the migration tool own the schema so Hibernate doesn't also try to generate DDL. You can set it to 'validate' to assert entities match the migrated schema.
  • Why can tests pass on H2 but the same migrations fail in production Postgres?
    Migration scripts often use vendor-specific DDL/types (JSONB, SERIAL, ON CONFLICT). H2's compatibility modes emulate these imperfectly, so H2 accepts DDL Postgres would reject — or vice versa. Testcontainers with real Postgres eliminates the gap.
  • How do you catch entity/schema drift automatically in a repository test?
    Set spring.jpa.hibernate.ddl-auto=validate so Hibernate verifies every mapped entity has a matching table/column in the migrated schema and fails context startup otherwise.

saying these in an interview costs you the question

  • Thinking you must create tables manually for @DataJpaTest
  • Not knowing Flyway/Liquibase run automatically inside the slice
  • Assuming create-drop in tests guarantees migrations are correct
  • Believing H2 faithfully runs Postgres/MySQL migration DDL

context