This error occurs when Liquibase detects a changeset identifier that has already been deployed to your PostgreSQL database. It happens when you try to re-run a migration that Liquibase has marked as executed, or when duplicate changesets are present in your changelog files.
Liquibase tracks all applied migrations in a special DATABASECHANGELOG table. Each changeset is uniquely identified by a combination of the changelog file path, author ID, and changeset ID. When Liquibase encounters a changeset it has already executed, it checks the changeset's content against what's recorded. If the changeset has been modified since it was first applied, or if duplicate identifiers exist, Liquibase raises this error to prevent unpredictable behavior.
Connect to your PostgreSQL database and check if the changeset is already recorded:
```sql
SELECT id, author, filename, exectype, orderexecuted
FROM databasechangelog
WHERE id = 'your_changeset_id' AND author = 'your_author'
ORDER BY orderexecuted DESC;
Add preconditions to your changesets to prevent re-execution of idempotent operations. This is the best practice for complex migrations.
Example XML changelog:
<changeSet id="001" author="myauthor">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="my_table" />
</not>
</preConditions>
<createTable tableName="my_table">
<column name="id" type="INT" />
</createTable>
</changeSet>The onFail="MARK_RAN" tells Liquibase to skip the changeset if the precondition fails, preventing the error.
If a changeset was modified after deployment and you want Liquibase to accept the new version, add the valid checksum:
liquibase calculate-checksum --changelog-file=db/changelog.xml --changeset-identifier=001::myauthorThen add this checksum to your changeset:
<changeSet id="001" author="myauthor">
<validCheckSum>9:c4a3293f78f30058f9144aeb8186beb7</validCheckSum>
<!-- Rest of your changeset -->
</changeSet>If your changeset uses property substitution that changes between environments, set the runOnChange attribute:
<changeSet id="001" author="myauthor" runOnChange="true">
<sql>
UPDATE my_table SET config_value = '${env.config}'
</sql>
</changeSet>Alternatively, use runAlways="true" if the changeset should execute every time Liquibase runs.
Search your changelog files for duplicate IDs:
grep -r 'id="' db/changelog/ | sort | uniq -dFor each duplicate, rename one of them to have a unique ID:
<!-- Before -->
<changeSet id="001" author="myauthor">
<!-- changes -->
</changeSet>
<!-- After -->
<changeSet id="001-renamed" author="myauthor">
<!-- changes -->
</changeSet>When merging branches with conflicting changelog files:
1. Don't modify existing changeset IDs
2. Append new changesets instead of editing old ones
3. Use a numbering scheme that prevents collisions (e.g., timestamps: id="202501281000")
4. Verify no duplicate IDs exist before committing:
grep -oh 'id="[^"]*"' db/changelog/*.xml | sort | uniq -c | grep -v '^ *1 'If you upgraded the PostgreSQL JDBC driver and now get 'relation already exists' errors:
1. Check your JDBC driver version:
- Current version: 42.7.5+ (check pom.xml or build.gradle)
2. Verify uppercase database names in connection string:
jdbc:postgresql://localhost:5432/MYDB // Problematic
jdbc:postgresql://localhost:5432/mydb // Correct3. Either:
- Use lowercase database names, OR
- Downgrade to PostgreSQL JDBC 42.7.4 temporarily, OR
- Wait for a Liquibase/driver patch
4. Test with:
liquibase status --db-url=jdbc:postgresql://localhost:5432/mydbIf you're on Liquibase 4.25.1 or later and have duplicate changesets that can't be merged immediately:
liquibase update --allow-duplicated-changeset-identifiers=trueThis allows Liquibase to deploy multiple changesets with the same ID, but only creates one database changelog entry. Use this as a temporary measure while you work to make IDs unique.
Checksum Calculation: Liquibase computes an MD5 checksum of each changeset's content. Any whitespace, comment, or formatting change invalidates the checksum, even if the SQL is functionally identical. Use calculate-checksum to debug.
Roll Forward Best Practice: Instead of modifying old changesets, always roll forward with new ones. If you need to rename a table created by changeset 001, create changeset 002 with the rename operation. This maintains audit trail and prevents checksum conflicts.
DATABASECHANGELOG Table: This is the source of truth. If it gets corrupted or out of sync with your changelog files, you'll encounter these errors. In rare cases, a manual SQL fix is needed, but document why and communicate with your team.
Preconditions vs. Error Handling: Use preconditions (recommended) rather than failOnError="false". Preconditions are explicit and saferβthey skip changesets gracefully without masking real errors.
CI/CD Pipelines: Always ensure your changelog files are merged and validated before pushing. Use a merge-conflict resolution strategy that enforces unique changeset IDs and prevents manual edits to non-current changesets.
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL
ERROR 42501: must be owner of table
How to fix "must be owner of table" in PostgreSQL
trigger cannot change partition destination
How to fix "Trigger cannot change partition destination" in PostgreSQL
SSL error: certificate does not match host name
SSL error: certificate does not match host name in PostgreSQL
No SSL connection
No SSL connection to PostgreSQL