An expand migration adds schema capacity while the old application version can still write rows.
Spring database rollout: add a nullable column before changing every writer
The checked expansion
SchemaEvolutionContractTest starts with receipt_id and amount_minor, then adds nullable tenant_id. It inserts an old-shape row that names only the original columns. H2 accepts the row, leaving tenant_id null. This proves the local expansion preserves old-writer compatibility for that one insert shape.
The fixture then backfills both existing rows to legacy. The backfill lesson explains why a production update needs batches and a tenant ownership rule. The Java migration page describes versioned application boundaries; this Spring page focuses on the datasource behavior in the kit.
Roll forward in stages
A deployed sequence usually needs: add a nullable column, deploy code that writes it, backfill historical rows, verify absence of nulls, then add a required constraint. If old instances remain during a rolling release, contracting early breaks them. The test demonstrates exactly that rejection in the contract phase.
H2 DDL and production DDL differ. Large table rewrites, lock duration, replication lag and rollback rules need target-database tests. The source kit invokes SQL directly; it does not run a migration tool or store a schema history.
Checked source
alter table shipment_receipt add column tenant_id varchar(40);
insert into shipment_receipt(receipt_id, amount_minor) values (102, 500);Verification boundary
SchemaEvolutionContractTest.nullableExpansionAcceptsOldWriterAndBackfillPreservesRows. The excerpt is shortened or a labelled design sketch; the kit contains the checked tests.
Costs and limits
One H2 table and two rows are checked. No rolling deployment, target-database lock estimate or versioned migration runner is present.
Common Mistakes
- Do not add NOT NULL before old writers are gone or updated.
- Do not use select-star assumptions across a schema rollout.
- Do not treat H2 DDL cost as a production estimate.
Read next
Spring database backfill: assign historical rows before enforcing ownership, Spring database contract phase: reject old writers only after backfill, Spring Boot migrations: what a versioned runner must add to the SQL test, Java versioned data changes: checksums and a committed ledger.
Checked file and migration continuation
Continue with Flyway expand and contract: show exactly when an old writer breaks.
