Spring Boot Integration: Zero-Config Setup and Full Properties Reference
Running Liquibase from the CLI is fine for learning and for standalone scripts. In a real Spring Boot application, you want migrations to run automatically at startup — before the application code touches the database. Spring Boot’s Liquibase auto-configuration handles this with zero boilerplate: add the dependency, point to your changelog, and migrations run before the application context is ready.
This article covers the complete integration: Maven/Gradle setup, how auto-run works and why, the full spring.liquibase.* property reference, environment-specific configuration with Spring profiles, the Maven plugin for CLI-equivalent commands, and the Actuator endpoint.
Add the Dependency
Spring Boot’s dependency management includes a tested Liquibase version. You only need to declare liquibase-core — no version required.
Maven (pom.xml):
<dependencies>
<!-- Liquibase -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<!-- version managed by Spring Boot BOM -->
</dependency>
<!-- MySQL JDBC Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Data JPA (for the datasource) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
Gradle (build.gradle.kts):
dependencies {
implementation("org.liquibase:liquibase-core")
runtimeOnly("com.mysql:mysql-connector-j")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}
That is the entire setup required. Spring Boot detects liquibase-core on the classpath and activates auto-configuration automatically.
Project Directory Structure
Place changelogs under src/main/resources. Spring Boot’s default changelog path is classpath:db/changelog/db.changelog-master.yaml — using this path means you never need to configure spring.liquibase.change-log at all.
src/
└── main/
├── java/
│ └── com/example/ecommerce/
│ └── EcommerceApplication.java
└── resources/
├── application.yml
├── application-dev.yml
├── application-prod.yml
└── db/
└── changelog/
├── db.changelog-master.yaml ← Spring Boot's default path
└── migrations/
└── 2026/
└── 05/
├── 001-create-users-table.yaml
└── 002-create-products-table.yaml
The master changelog uses includeAll to pick up migration files automatically:
# db/changelog/db.changelog-master.yaml
databaseChangeLog:
- includeAll:
path: db/changelog/migrations/
relativeToChangelogFile: false
How Auto-Run Works
When your Spring Boot application starts, the following sequence happens:
- Spring Boot creates the
DataSourcebean (connection pool to MySQL) - Liquibase auto-configuration detects
liquibase-coreon the classpath - A
SpringLiquibasebean is created using the applicationDataSource SpringLiquibaserunsliquibase update— exactly as if you had run it from the CLI- Only after Liquibase finishes does Spring continue creating other beans (JPA, repositories, controllers)
This ordering is critical. JPA entity classes are mapped to database tables — those tables must exist before JPA tries to validate or use them. Liquibase running before the rest of the context guarantees this.
The @DependsOn relationship is handled automatically by Spring Boot’s auto-configuration. You do not need to add any annotations.
Mandatory: Disable Hibernate DDL
This is the most common mistake when adopting Liquibase in a Spring Boot project: forgetting to disable Hibernate’s schema management.
By default, Spring Boot sets spring.jpa.hibernate.ddl-auto to create-drop for embedded databases (H2) and none for everything else. But if you have ever set it to update, create, or validate in your application.yml, you must change it to none now.
spring:
jpa:
hibernate:
ddl-auto: none # Liquibase owns the schema — Hibernate must not touch it
Running both Liquibase and Hibernate DDL simultaneously causes:
- Hibernate creating tables before Liquibase runs (Liquibase then fails because the table exists)
- Hibernate silently adding columns that Liquibase doesn’t know about (schema drift)
validatemode failing because Hibernate sees Liquibase-managed constraints it doesn’t recognise
Set ddl-auto: none permanently in all environments. Liquibase is now the sole owner of the schema.
Minimal application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/ecommerce?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: lb_user
password: lb_pass
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: none
open-in-view: false
liquibase:
enabled: true
# change-log defaults to classpath:db/changelog/db.changelog-master.yaml
# No other properties needed for local dev
Start the application:
./mvnw spring-boot:run
You will see log lines like:
INFO liquibase.changelog : Reading from ecommerce.DATABASECHANGELOG
INFO liquibase.changelog : Table users created
INFO liquibase.changelog : ChangeSet db/changelog/migrations/2026/05/001-create-users-table.yaml::001::abhay ran successfully
The application starts only after all pending changesets are applied.
Full spring.liquibase.* Properties Reference
Every configurable property with its default value and when to use it:
Core
| Property | Default | Description |
|---|---|---|
spring.liquibase.enabled | true | Set to false to skip Liquibase entirely. Use in tests that manage their own schema. |
spring.liquibase.change-log | classpath:db/changelog/db.changelog-master.yaml | Path to the master changelog. Override when your layout differs from the default. |
spring.liquibase.default-schema | (none) | Default schema for application tables. Required for multi-schema databases. |
spring.liquibase.liquibase-schema | (none) | Schema where DATABASECHANGELOG and DATABASECHANGELOGLOCK are created. Separate from default-schema to keep tracking tables in a dedicated schema. |
Connection Override
By default, Liquibase uses the application DataSource. Override when Liquibase needs a different user (e.g., a migration user with DDL privileges that the app user lacks):
| Property | Default | Description |
|---|---|---|
spring.liquibase.url | (from DataSource) | JDBC URL for Liquibase only |
spring.liquibase.user | (from DataSource) | Username for Liquibase only |
spring.liquibase.password | (from DataSource) | Password for Liquibase only |
spring.liquibase.driver-class-name | (from DataSource) | JDBC driver class for Liquibase only |
Using a separate Liquibase user is a security best practice: the application user at runtime needs only SELECT/INSERT/UPDATE/DELETE, while the migration user needs CREATE/ALTER/DROP. This limits blast radius if the application is compromised.
Filtering
| Property | Default | Description |
|---|---|---|
spring.liquibase.contexts | (none) | Comma-separated list of contexts to activate. Only changesets matching an active context run. E.g., dev runs only context: dev changesets. |
spring.liquibase.label-filter | (none) | Label expression filter. E.g., !slow skips changesets labelled slow. |
Tracking Table Names
| Property | Default | Description |
|---|---|---|
spring.liquibase.database-change-log-table | DATABASECHANGELOG | Rename the tracking table. Useful when integrating with a DB that has naming rules. |
spring.liquibase.database-change-log-lock-table | DATABASECHANGELOGLOCK | Rename the lock table. |
Behaviour Flags
| Property | Default | Description |
|---|---|---|
spring.liquibase.drop-first | false | Drop the entire schema before running Liquibase. Never set to true in production. Useful in test environments to start with a clean state. |
spring.liquibase.test-rollback-on-update | false | After applying all changesets, immediately rolls them back and re-applies. Equivalent to liquibase updateTestingRollback. Use in CI to validate rollback blocks. |
spring.liquibase.clear-checksums | false | Clears all checksums before running. Same as liquibase clearCheckSums. Use only when fixing cosmetic changeset edits. |
spring.liquibase.rollback-file | (none) | Path to a file where rollbackSQL output is written during update. Provides a ready-made rollback script after every deployment. |
Parameters and Tag
| Property | Default | Description |
|---|---|---|
spring.liquibase.parameters.* | (none) | Key-value pairs substituted into changelog ${property} placeholders. Alternative to liquibase.properties substitution. |
spring.liquibase.tag | (none) | Tag to apply to the database after a successful update. Automates the pre-deployment tagging step covered in Article 5. |
Environment-Specific Configuration with Spring Profiles
Use Spring profile-specific files to change Liquibase behaviour per environment without duplicating common config.
application.yml (shared baseline):
spring:
datasource:
url: jdbc:mysql://localhost:3306/ecommerce?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: lb_user
password: lb_pass
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: none
liquibase:
enabled: true
contexts: default
application-dev.yml:
spring:
liquibase:
contexts: dev,default
drop-first: false # Set to true only when you want a full reset
test-rollback-on-update: false
application-staging.yml:
spring:
liquibase:
contexts: staging,default
test-rollback-on-update: true # Validate rollback blocks in staging before prod
tag: ${APP_VERSION:staging} # Auto-tag with app version env var
rollback-file: /tmp/rollback-${APP_VERSION}.sql
application-prod.yml:
spring:
liquibase:
contexts: prod,default
url: ${DB_MIGRATION_URL} # Separate migration user with DDL privileges
user: ${DB_MIGRATION_USER}
password: ${DB_MIGRATION_PASSWORD}
tag: ${APP_VERSION} # Always tag production deployments
rollback-file: /var/log/app/liquibase-rollback-${APP_VERSION}.sql
Activate a profile at startup:
# Spring Boot CLI
java -jar app.jar --spring.profiles.active=prod
# Environment variable
SPRING_PROFILES_ACTIVE=prod java -jar app.jar
# Maven
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
Injecting Context-Specific Seed Data
Contexts let you run environment-specific changesets without multiple changelogs. For example, seed data for development that must never run on production:
# db/changelog/migrations/2026/05/099-seed-dev-data.yaml
databaseChangeLog:
- changeSet:
id: "099"
author: abhay
context: dev
comment: Seed development test data — never runs in prod
changes:
- insert:
tableName: users
columns:
- column: {name: email, value: "dev@example.com"}
- column: {name: full_name, value: "Dev User"}
- column: {name: password_hash, value: "$2a$10$..."}
- column: {name: role, value: "admin"}
- column: {name: status, value: "active"}
rollback:
- delete:
tableName: users
where: email = 'dev@example.com'
With spring.liquibase.contexts=prod, this changeset is skipped. With contexts=dev,default, it runs.
The Liquibase Maven Plugin
The Maven plugin lets you run Liquibase CLI commands (update, rollback, status, diff) via Maven goals. This is useful when you want to run migrations outside the application context — e.g., in CI before starting the application, or for generating preview SQL.
Add to pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>${liquibase.version}</version>
<configuration>
<changeLogFile>src/main/resources/db/changelog/db.changelog-master.yaml</changeLogFile>
<driver>com.mysql.cj.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/ecommerce?useSSL=false&serverTimezone=UTC</url>
<username>lb_user</username>
<password>lb_pass</password>
<promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
</configuration>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Common Maven goals:
# Check pending changesets
./mvnw liquibase:status
# Preview SQL without applying
./mvnw liquibase:updateSQL
# Apply migrations
./mvnw liquibase:update
# Tag before deployment
./mvnw liquibase:tag -Dliquibase.tag=v1.1.0
# Roll back to tag
./mvnw liquibase:rollback -Dliquibase.rollbackTag=v1.0.0
# Validate changelog
./mvnw liquibase:validate
Maven vs Spring Boot auto-run — when to use each:
| Scenario | Use |
|---|---|
| Apply migrations at app startup | Spring Boot auto-run (spring.liquibase.*) |
| Run migrations in CI before deploying | Maven plugin (liquibase:update) |
| Preview SQL before a production deployment | Maven plugin (liquibase:updateSQL) |
| Roll back after a failed deploy | Maven plugin (liquibase:rollback) |
| Generate a rollback script for the ops team | Maven plugin (liquibase:updateSQL output) |
Spring Boot Actuator: /actuator/liquibase
If spring-boot-starter-actuator is on the classpath, Spring Boot exposes a Liquibase endpoint that shows the full changelog history via HTTP.
Add actuator:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Expose the endpoint in application.yml:
management:
endpoints:
web:
exposure:
include: health, info, liquibase
Query it:
curl http://localhost:8080/actuator/liquibase | jq .
{
"contexts": {
"application": {
"liquibaseBeans": {
"liquibase": {
"changeSets": [
{
"id": "001",
"author": "abhay",
"changeLog": "db/changelog/migrations/2026/05/001-create-users-table.yaml",
"dateExecuted": "2026-05-03T12:00:00.000+00:00",
"deploymentId": "7243819023",
"execType": "EXECUTED",
"checkSum": "9:abc123...",
"tag": null
}
]
}
}
}
}
}
This is the same data as liquibase history or SELECT * FROM DATABASECHANGELOG, accessible over HTTP. Restrict this endpoint in production — it exposes your entire schema change history.
# Production: require authentication for the liquibase endpoint
management:
endpoint:
liquibase:
access: read-only # Spring Boot 4.x
Testing: Disable Liquibase for Unit Tests
Tests that mock repositories or use an in-memory H2 don’t need Liquibase. Disable it per-test class or globally for the test profile:
Option 1 — Per test class:
@SpringBootTest
@TestPropertySource(properties = "spring.liquibase.enabled=false")
class UserServiceTest {
// Tests that mock the repository — no real DB needed
}
Option 2 — Test profile (src/test/resources/application-test.yml):
spring:
liquibase:
enabled: false
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
jpa:
hibernate:
ddl-auto: create-drop # OK for tests — H2 schema managed by Hibernate
Option 3 — Integration tests with a real database (recommended):
For tests that actually exercise SQL queries, run Liquibase against a real MySQL instance via Testcontainers. This is covered in depth in Article 20.
@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("ecommerce")
.withUsername("lb_user")
.withPassword("lb_pass");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
}
// Liquibase runs against the Testcontainer MySQL automatically
}
Common Mistakes
Leaving ddl-auto: update alongside Liquibase: Hibernate silently adds columns, modifies types, and creates join tables without going through Liquibase. Your schema diverges from the changelog invisibly. Set ddl-auto: none and let every schema change flow through a changeset.
Using the same database user for the app and for migrations: The app user needs only DML privileges at runtime. The migration user needs DDL. Using one user for both means your running application has DROP TABLE access — a serious security exposure. Use spring.liquibase.url/user/password to configure a separate migration credential in production.
Setting drop-first: true and forgetting to revert it: This property drops every table in the schema before running migrations. It is useful exactly once in a test environment to reset state. If it is left true and reaches a shared environment, it destroys the database. Always scope it to a test-only profile and never commit it as true to the baseline config.
Best Practices
- Default changelog path (
classpath:db/changelog/db.changelog-master.yaml) means zero extra config — stick to it unless you have a specific reason not to ddl-auto: nonealways — write it inapplication.ymland never override it toupdate,create, orvalidate- Separate migration user in staging and production —
spring.liquibase.user/passwordfor DDL, applicationDataSourcefor DML spring.liquibase.tagin prod profile — auto-tag every deployment with the app version for instant rollback targetsspring.liquibase.rollback-filein prod profile — write the rollback SQL file to disk during every deployment; it is ready when you need ittest-rollback-on-update: truein staging — validate rollback blocks before the code reaches production- Protect
/actuator/liquibase— it exposes your full schema history; require auth in production
What You’ve Learned
- Adding
liquibase-coreto Maven/Gradle activates Spring Boot auto-configuration with no further wiring - Liquibase runs before the application context is fully ready — tables exist before JPA validates them
spring.jpa.hibernate.ddl-auto: noneis mandatory when using Liquibase — two schema managers will conflict- The default changelog path is
classpath:db/changelog/db.changelog-master.yaml— nochange-logproperty needed if you use it - Spring profiles allow environment-specific Liquibase behaviour: dev contexts for seed data, prod profile for separate migration user and auto-tagging
- The Maven plugin runs Liquibase commands outside the application context — use it for CI pipelines and manual rollbacks
/actuator/liquibaseexposes the full changelog history over HTTP — secure it in production
Part 1 complete. You now have the full foundation: why database versioning matters, how Liquibase tracks changes, which changelog format to use, how to run your first migration, what every core command does, and how Spring Boot integrates it all at startup.
Next: Article 7 — Changelog Organization: Master Files, include/includeAll, Directory Structures — the first article of Part 2, where you learn how to structure changelogs for a team working across multiple features and releases simultaneously.