Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Flyway expand and contract: show exactly when an old writer breaks

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

An expand-and-contract rollout keeps old writes valid while a new nullable column is introduced, then rejects them only after the constraint is tightened.

Download Spring source kit

Version 2 is the overlap window

V1 creates shipment_receipt with receipt_id and amount_minor. V2 adds nullable tenant_id. After Flyway applies V2, the test inserts receipt 102 without tenant_id; both the seed and new row have null tenant_id. That is intentional. The old writer still runs, so a new reader must not assume every historical row has an owner.

V3 assigns legacy to the null rows and then sets tenant_id NOT NULL. The test checks that no null rows remain, the old insert fails and a new insert with tenant_id=east succeeds. The direct SQL expansion check and the constraint lesson explain each phase without migration tooling.

The fixture is smaller than a deployment

The test performs a backfill and NOT NULL alteration in one tiny H2 migration. A large production table may require resumable batches, index planning and an application release between backfill and constraint. New writers should deploy before V3 and old writers should retire before V3. The fixture checks SQL behavior, not that rollout timing.

When two application versions coexist, tenant-scoped reads need a policy for still-null rows. Guessing the tenant from a default can make authorization consistently wrong. The tenant predicate shows why ownership must be established before scoped queries trust it.

Checked source

sql
-- V2__expand_tenant.sql
alter table shipment_receipt add column tenant_id varchar(40);
-- V3__backfill_and_contract.sql
update shipment_receipt set tenant_id = 'legacy' where tenant_id is null;
alter table shipment_receipt alter column tenant_id set not null;

Verification boundary

FlywayReceiptMigrationTest.orderedFilesPreserveTheOldWriterUntilContract. The excerpt is shortened or a labelled design sketch; the kit contains the checked tests.

Costs and limits

Two rows and one H2 process do not measure lock time, batch cost, data ownership accuracy, mixed-version processes or recovery from an interrupted migration.

Common Mistakes

  • Do not require the new column in the same step that introduces it to an old writer.
  • Do not assign a guessed tenant to production records.
  • Do not infer target-engine DDL lock behavior from H2.

Read next

Flyway SQL migrations: record ordered schema changes against a real database, Spring database backfill: assign historical rows before enforcing ownership, Spring database contract phase: reject old writers only after backfill, Spring JdbcTemplate tenant predicates: put ownership in the SQL query.

Continue with checked Boot startup

Continue with Spring Boot migration target: test an old writer before closing the schema.

spring
spring-boot
migration
Storage details