Spring Boot: Flyway schema migrations
Review generated migrations
Executing a migration also depends on the project's Flyway, datasource, database contents and deployment configuration. Generated SQL must therefore be reviewed and tested before use.
The SpringBoot platform can derive PostgreSQL-oriented Flyway SQL and a JSON representation of the desired schema from a component's persistence model. The versioned JSON files form the generator's schema history; they are not database migrations themselves.
Two modes support different stages of the workflow:
- STRICT creates versioned SQL and matching versioned schema snapshots.
- DEVELOPMENT replaces one temporary repeatable migration and one temporary schema snapshot for local iteration.
1. Modeling persistence with schema versions
Flyway migration generation is driven by the persists block of a component.
package org.example.customer.backend
import org.joinedworkz.facilities.profiles.springboot
import org.joinedworkz.facilities.common.profiles.java
import org.example.customer.api
import org.example.customer.domain
platform SpringBoot
component CustomerBackend {
provide /api/v1/customers
persists Main version=1 {
...org.example.customer.domain
}
}The version value is the integer schema version of this persistence model. If it is omitted, the initial value is 1.
JoinedWorkz uses this version to:
- generate versioned Flyway migration files,
- generate schema snapshot JSON files,
- validate whether schema changes and version changes are consistent.
1.1 One persistence per Flyway history
The documented contract supports exactly one persistence model per effective Flyway history and outlet pair. Do not direct multiple persists blocks to the same migration and snapshot directories:
- DEVELOPMENT uses one fixed pair of working filenames and a later persistence would replace the earlier output;
- independently versioned SQL files in one Flyway location can use the same Flyway version and conflict.
Use a separate generation unit and separate Flyway history for another persistence model.
2. Generated artifacts
For a persistence named Main, JoinedWorkz generates:
In STRICT mode
V1__main_init.sqlV2__main_migration.sqlV1_main_schema.jsonV2_main_schema.json
In DEVELOPMENT mode
R__DEV_work.sqlDEV_main_schema.json
The two artifact types belong in separate outlets:
generatedSchemaMigrationcontains SQL that Flyway can discover;generatedSchemaSnapshotcontains the JSON history used by JoinedWorkz to calculate later differences.
Only the SQL outlet is a Flyway runtime location. Versioned files in the snapshot outlet are generator inputs for subsequent runs.
3. Recommended outlet configuration
Route both stateful outlets to dedicated persistent directories. The SQL directory can be registered as a project resource for Flyway; the snapshot directory must remain available to future JoinedWorkz generation runs.
See the central joinedworkz.properties reference for the file location, outlet path contract and schemaMigration.mode value rules.
outlet.generatedSchemaMigration.directory=../backend-spring-generated/src/main/resources/db/migration
outlet.generatedSchemaSnapshot.directory=../backend-spring-generated/src/main/resources/db/schema
schemaMigration.mode=DEVELOPMENTRecommended target directories:
- migrations:
src/main/resources/db/migration - schema snapshots:
src/main/resources/db/schema
Do not include either complete directory in generic generated-output cleanup. It can contain persistent versioned history even when the current mode creates replaceable development files.
Do not retain the default nested paths for versioned history
The SpringBoot defaults place these stateful outlets below src/generated/resources, which is also the replaceable generatedConfiguration tree. A Studio full generation can clear that parent outlet. Route migrations and snapshots to the dedicated persistent directories shown above before relying on full-directory cleanup.
The versioned SQL and matching versioned snapshot must be committed together. The DEVELOPMENT pair is temporary and normally remains uncommitted. The central ownership matrix describes the corresponding cleanup and version-control rules.
4. Migration modes
schemaMigration.mode selects the migration mode. If the property is missing or blank, JoinedWorkz uses STRICT. Values are trimmed and compared case-insensitively. The uppercase forms below are recommended for consistent project configuration. Any other value stops generation with a configuration error.
4.1 STRICT
schemaMigration.mode=STRICTSTRICT mode is the normal mode for accepting a change into versioned migration history.
It:
- finds the latest versioned snapshot for the persistence in the configured snapshot outlet,
- compares it with the current persistence model,
- validates the relationship between the detected schema difference and the modeled version,
- creates the next versioned SQL file and matching snapshot when that relationship is valid.
The enforced version rules are:
- With no previous snapshot, version
1createsV1__main_init.sqlandV1_main_schema.json; any higher initial version is rejected. - With an unchanged schema and unchanged version, no new files are generated.
- Increasing the version without a schema change is rejected.
- A schema change without increasing the version is rejected.
- A schema change requires exactly the previous version plus one. Skipping a version is rejected.
For example, an existing V1_main_schema.json, a changed persistence model and version=2 produce:
V2__main_migration.sqlV2_main_schema.json
The same-named versioned files are configured as non-overwriting output. This does not make STRICT a complete history-integrity check: migration SQL is not used to calculate the difference, and JoinedWorkz does not prove that every SQL file and snapshot in an existing history still form a matching pair. Preserving and reviewing accepted history remains a project responsibility.
STRICT also refuses to generate versioned output while either of these exact development artifacts exists in its effective outlet:
R__DEV_work.sqlDEV_main_schema.json
Remove both only when the development change is ready to be regenerated as the next versioned migration. Development files left in a different, obsolete directory are not part of this check.
4.2 DEVELOPMENT
schemaMigration.mode=DEVELOPMENTDEVELOPMENT mode is intended for local, temporary iteration before accepting the next schema version.
It:
- uses the latest versioned snapshot as its baseline,
- compares it with the current persistence model,
- deletes and recreates the fixed development pair,
- does not create new versioned migration files.
The generated files are:
R__DEV_work.sqlDEV_main_schema.json
DEV_main_schema.json records the current working state but is not the baseline for the next generation run. JoinedWorkz recalculates every development migration from the latest versioned snapshot. If no versioned snapshot exists, the development SQL contains creation statements for the complete modeled schema.
Use a reset or disposable development database
Flyway can execute a changed repeatable migration again against a database that already contains the result of an earlier development version. Operations such as renames, type changes and constraint changes are not generally safe to repeat. Reset the development database to the accepted versioned baseline before testing each regenerated R__DEV_work.sql.
5. Safe workflow
5.1 Establish the initial version
- Model the persistence with
version=1. - Use
schemaMigration.mode=STRICT. - Generate
V1__main_init.sqlandV1_main_schema.json. - Review the SQL and test it against a fresh target database.
- Accept and commit the SQL and snapshot together.
5.2 Develop the next change
- Keep the currently accepted version in the model.
- Set
schemaMigration.mode=DEVELOPMENT. - Change the persistence model and regenerate.
- Reset a disposable database to the accepted versioned history.
- Review and test
R__DEV_work.sql. - Repeat from the accepted database baseline while refining the change.
Only the two development files are replaced during this phase. Existing versioned SQL and snapshots must remain unchanged.
5.3 Accept the next version
When the change is ready:
- Increase the modeled version by exactly one.
- Remove
R__DEV_work.sqlandDEV_main_schema.json, but retain all versioned files. - Switch to
schemaMigration.mode=STRICT. - Regenerate the next versioned SQL and snapshot.
- Review the SQL.
- Test both a fresh installation and an upgrade from the previously accepted database version.
- Commit the new SQL and snapshot together.
Never change an already deployed versioned migration. If a generated migration needs data movement, a cast, reordered operations or another correction, make and test that adjustment before the migration is accepted. Its final database structure must still match the committed schema snapshot.
6. Existing history and schema snapshots
Versioned snapshots are persistent generator history. For Main, keep files such as these directly in the configured snapshot outlet:
V1_main_schema.jsonV2_main_schema.json
JoinedWorkz selects the highest matching versioned snapshot and compares that state with the current persistence model. Do not move, rename, clean or manually edit accepted snapshots. A missing or altered snapshot can cause JoinedWorkz to calculate from the wrong baseline or reject the next version.
The snapshot field schemaName identifies the logical persistence schema in the generator model. It does not select or create a PostgreSQL database schema. The generated SQL uses unqualified table names; the effective PostgreSQL schema is determined by the project's Flyway, datasource and database configuration.
7. Generated SQL and review boundary
JoinedWorkz can derive operations for:
- creating and removing tables and columns;
- simple table and column rename candidates;
- column type, nullability and default changes;
- primary keys, foreign keys, unique constraints and indexes.
This is structural generation, not a substitute for migration design. Review every SQL file for at least:
- destructive table or column removal;
- whether an inferred rename expresses the intended data transition;
- casts required for type changes;
- data backfills before adding a non-null constraint;
- defaults, constraint ordering and existing production data;
- PostgreSQL syntax and naming conflicts.
Do not execute or deploy generated SQL solely because generation succeeded. Database execution is a separate project test.
8. Known limitations
8.1 PostgreSQL-oriented output
The schema model and renderer currently target PostgreSQL conventions, including types such as UUID, JSONB and TIMESTAMPTZ. Direct UUID columns and reference columns targeting UUID identifiers are rendered as parameterless UUID; modeled length metadata is not appended to that SQL type. This page makes no compatibility claim for other database systems and no claim that generation alone proves a successful PostgreSQL migration.
8.2 Rename inference
Rename operations are inferred from structurally matching removed and added objects; the model does not explicitly declare migration identity. Ambiguous or combined changes can therefore become drop/create operations, and an inferred rename can still differ from the intended data migration. Always inspect these operations before accepting them.
8.3 ID generation strategies
The SpringBoot generation property controls Java persistence annotations and, for CUSTOM, generated data-access code. It does not cause the schema renderer to add a sequence, identity definition, generator table or database-side UUID default. A strategy change can therefore produce no structural Flyway diff.
Maintain database objects required by AUTO, IDENTITY, SEQUENCE or TABLE as application migration design and test them with the selected JPA provider and database. The separate modeling and runtime contract is documented under ID generation strategies.
9. Troubleshooting checklist
Use the central Troubleshooting reference to distinguish migration generation from a later Flyway/database runtime failure.
If generation does not produce the expected next version:
- confirm that migration and snapshot outlets point to the intended persistent directories;
- confirm that exactly one persistence contributes to this Flyway history;
- inspect the latest
V*_<persistence>_schema.jsonin the snapshot outlet; - ensure that the modeled version is unchanged when there is no schema change, or exactly one higher when there is a schema change;
- remove both development files before switching to STRICT;
- preserve every previously accepted versioned SQL file and snapshot;
- inspect the complete generator error before changing or deleting history.
