What's Actually Happening

Liquibase database update fails during execution. Database schema changes are not applied successfully.

The Error You'll See

```bash $ liquibase update

ERROR: Validation Failed: 1 change sets check sum my-changelog.xml::1::author was: 8:abc123 but is now: 8:def456 ```

Lock error:

bash
ERROR: Could not acquire change log lock

SQL error:

bash
ERROR: Execution of "CREATE TABLE users..." failed: relation "users" already exists

Changelog not found:

bash
ERROR: The file my-changelog.xml was not found in the search path

Why This Happens

  1. 1.Checksum mismatch - Changelog modified after execution
  2. 2.Lock not released - Previous run didn't complete
  3. 3.SQL syntax error - Invalid SQL in changeset
  4. 4.Duplicate changeset - ID already used
  5. 5.Missing dependency - Referenced file not found
  6. 6.Permission denied - User lacks permissions

Step 1: Check Database Status

```bash # View Liquibase history: liquibase history

# Check databasechangelog table: psql -c "SELECT id, author, filename, dateexecuted, checksum FROM databasechangelog ORDER BY dateexecuted DESC LIMIT 10;"

# Check databasechangeloglock: psql -c "SELECT * FROM databasechangeloglock;"

# View status: liquibase status

# Check unexpected changesets: liquibase unexpectedChangeSets

# List pending changesets: liquibase status --verbose

# Validate changelog: liquibase validate ```

Step 2: Fix Checksum Mismatch

```bash # Checksum mismatch means changeset was modified after execution

# View mismatched changesets: liquibase validate

# Option 1: Revert changeset to original: git checkout my-changelog.xml

# Option 2: Clear checksums: liquibase clearCheckSums

# Option 3: Update checksum in database: psql -c "UPDATE databasechangelog SET checksum = NULL WHERE id = '1' AND author = 'author';"

# Option 4: Add new changeset instead: # Don't modify existing changeset # Create new changeset: <changeSet id="2" author="author"> <!-- New change --> </changeSet>

# Option 5: Use runOnChange for procedures/views: <changeSet id="1" author="author" runOnChange="true"> <createProcedure> CREATE OR REPLACE PROCEDURE my_proc() ... </createProcedure> </changeSet>

# Option 6: Use validCheckSum for accepted change: <changeSet id="1" author="author" validCheckSum="8:def456"> <!-- Modified changeset --> </changeSet> ```

Step 3: Release Database Lock

```bash # Check lock status: psql -c "SELECT locked, lockgranted, lockedby FROM databasechangeloglock;"

# If locked = true and no Liquibase running: liquibase releaseLocks

# Or manually: psql -c "UPDATE databasechangeloglock SET locked = false, lockgranted = null, lockedby = null;"

# Force unlock: liquibase --changeLogFile=my-changelog.xml releaseLocks

# Check for orphaned locks: psql -c "SELECT * FROM databasechangeloglock WHERE locked = true;"

# Clear lock after crash: liquibase --url=jdbc:postgresql://localhost/db releaseLocks

# Prevent lock issues: # Always let Liquibase complete # Use proper timeout settings ```

Step 4: Fix Changeset Errors

```bash # View the failing changeset: liquibase status --verbose

# Common changeset errors:

# 1. Duplicate changeset ID: # Each changeset must have unique id + author + filename <changeSet id="1" author="author"> <!-- First --> <changeSet id="1" author="author"> <!-- ERROR: duplicate -->

# Fix: Use unique ID <changeSet id="2" author="author">

# 2. SQL syntax error: # Error: syntax error at or near "..." # Fix: Review SQL, test manually

# 3. Object already exists: # Error: relation "users" already exists # Fix: Add preconditions: <preConditions onFail="MARK_RAN"> <not> <tableExists tableName="users"/> </not> </preConditions>

# 4. Missing dependency: # Error: relation "orders" does not exist # Fix: Ensure table created in earlier changeset

# 5. Invalid column reference: # Fix: Check column names match actual schema ```

Step 5: Handle Failed Changesets

```bash # If changeset failed during execution:

# Check failed changesets: liquibase status

# View error in databasechangelog: psql -c "SELECT id, author, comments FROM databasechangelog WHERE comments LIKE '%Error%';"

# Option 1: Fix SQL and retry: liquibase update

# Option 2: Mark as ran (if manually fixed): liquibase changeLogSync

# Option 3: Rollback failed changeset: liquibase rollback <tag>

# Option 4: Mark specific changeset as executed: liquibase markNextChangeSetRan

# Manual cleanup: # 1. Fix data in database # 2. Update databasechangelog: psql -c "UPDATE databasechangelog SET comments = 'Fixed manually' WHERE id = '1';"

# Then continue: liquibase update ```

Step 6: Fix Changelog Path Issues

```bash # Liquibase search path:

# Check changelog file exists: ls -la db/changelog/my-changelog.xml

# Use correct path: liquibase --changeLogFile=db/changelog/my-changelog.xml update

# Multiple changelog files: liquibase --changeLogFile=db/changelog-master.xml update

# Master changelog includes: <databaseChangeLog> <include file="db/changelog/changes/001-create-users.xml"/> <include file="db/changelog/changes/002-create-orders.xml"/> <includeAll path="db/changelog/changes/"/> </databaseChangeLog>

# Classpath for resources: liquibase --classpath=/opt/migrations update

# Use absolute path: liquibase --changeLogFile=/opt/migrations/changelog.xml update

# Check working directory: pwd # Run from project root ```

Step 7: Configure Connection Properly

```bash # Liquibase connection configuration:

# Command line: liquibase \ --url=jdbc:postgresql://localhost:5432/mydb \ --username=user \ --password=pass \ --changeLogFile=db/changelog.xml \ update

# Properties file (liquibase.properties): url=jdbc:postgresql://localhost:5432/mydb username=user password=pass changeLogFile=db/changelog.xml driver=org.postgresql.Driver

# Environment variables: export LIQUIBASE_URL=jdbc:postgresql://localhost:5432/mydb export LIQUIBASE_USERNAME=user export LIQUIBASE_PASSWORD=pass

# Spring Boot configuration: spring.liquibase.url=jdbc:postgresql://localhost:5432/mydb spring.liquibase.user=user spring.liquibase.password=pass spring.liquibase.change-log=classpath:db/changelog.xml

# Test connection: liquibase --url=jdbc:postgresql://localhost:5432/mydb --username=user --password=pass status ```

Step 8: Use Preconditions Properly

```xml <!-- Preconditions help avoid common errors: -->

<!-- Check table doesn't exist before creating --> <changeSet id="1" author="author"> <preConditions onFail="MARK_RAN"> <not> <tableExists tableName="users"/> </not> </preConditions> <createTable tableName="users"> <column name="id" type="int"> <constraints primaryKey="true"/> </column> </createTable> </changeSet>

<!-- Check column exists before modifying --> <changeSet id="2" author="author"> <preConditions onFail="HALT"> <columnExists tableName="users" columnName="email"/> </preConditions> <dropColumn tableName="users" columnName="email"/> </changeSet>

<!-- Check SQL condition --> <changeSet id="3" author="author"> <preConditions onFail="MARK_RAN"> <sqlCheck expectedResult="0"> SELECT COUNT(*) FROM users WHERE role = 'admin' </sqlCheck> </preConditions> <insert tableName="users"> <column name="role" value="admin"/> </insert> </changeSet>

<!-- onFail options: HALT, CONTINUE, MARK_RAN, WARN --> ```

Step 9: Handle Contexts and Labels

```xml <!-- Use contexts for environment-specific changes: -->

<changeSet id="1" author="author" context="dev"> <!-- Only runs in dev context --> </changeSet>

<changeSet id="2" author="author" context="prod"> <!-- Only runs in prod context --> </changeSet>

<changeSet id="3" author="author" context="dev, test"> <!-- Runs in dev OR test context --> </changeSet>

<!-- Run with specific context --> liquibase --contexts=dev update liquibase --contexts=prod update

<!-- Use labels for grouping --> <changeSet id="4" author="author" labels="version-1.0"> <createTable tableName="products"/> </changeSet>

<changeSet id="5" author="author" labels="version-1.0, version-1.1"> <!-- Multiple labels --> </changeSet>

<!-- Run specific labels --> liquibase --labels=version-1.0 update

<!-- Context expressions --> liquibase --contexts="dev and !test" update liquibase --contexts="dev or test" update ```

Step 10: Liquibase Verification Script

```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-liquibase.sh #!/bin/bash

CHANGELOG=${1:-"db/changelog.xml"}

echo "=== Liquibase Status ===" liquibase --changeLogFile=$CHANGELOG status 2>&1 | head -30

echo "" echo "=== Database Lock ===" psql -c "SELECT locked, lockgranted, lockedby FROM databasechangeloglock;" 2>/dev/null || echo "Cannot query lock"

echo "" echo "=== Recent History ===" psql -c "SELECT id, author, filename, dateexecuted FROM databasechangelog ORDER BY dateexecuted DESC LIMIT 10;" 2>/dev/null || echo "Cannot query history"

echo "" echo "=== Validation ===" liquibase --changeLogFile=$CHANGELOG validate 2>&1

echo "" echo "=== Changelog File ===" ls -la $CHANGELOG 2>/dev/null || echo "Changelog not found at $CHANGELOG"

echo "" echo "=== Pending Changesets ===" liquibase --changeLogFile=$CHANGELOG status --verbose 2>&1 | head -30

echo "" echo "=== Unexpected Changesets ===" liquibase --changeLogFile=$CHANGELOG unexpectedChangeSets 2>&1 | head -20

echo "" echo "=== Recommendations ===" echo "1. Run liquibase validate to check checksums" echo "2. Use liquibase releaseLocks if locked" echo "3. Clear checksums with liquibase clearCheckSums" echo "4. Check changeset IDs are unique" echo "5. Verify changelog file path" echo "6. Add preconditions for safety" echo "7. Use contexts for environment control" EOF

chmod +x /usr/local/bin/check-liquibase.sh

# Usage: /usr/local/bin/check-liquibase.sh db/changelog.xml ```

Liquibase Update Checklist

CheckExpected
Lock releaseddatabasechangeloglock.locked = false
Checksums validNo mismatch errors
Changelog foundFile exists in search path
Changeset IDsUnique
ConnectionDatabase reachable
PreconditionsAdded for safety
SQL validNo syntax errors

Verify the Fix

```bash # After fixing Liquibase update issues

# 1. Release lock if needed liquibase releaseLocks // Lock released

# 2. Validate changelog liquibase validate // No errors

# 3. Run update liquibase update // Changesets applied

# 4. Check status liquibase status // Up to date

# 5. Verify history psql -c "SELECT * FROM databasechangelog ORDER BY dateexecuted DESC LIMIT 5;" // Shows new changesets

# 6. Test application // Application works with new schema ```

  • [Fix Flyway Migration Failed](/articles/fix-flyway-migration-failed)
  • [Fix PostgreSQL Permission Denied](/articles/fix-postgresql-permission-denied)
  • [Fix MySQL Table Already Exists](/articles/fix-mysql-foreign-key-constraint)