Change Intelligence is coming.

  • Concept
  • Release notes

Liquibase Community 5.0.4 Release Notes

Liquibase Community v5.0.4 is a security-focused release: credentials are now redacted from error messages and logs, new opt-in lockdown flags let you restrict changelog features that execute code, and more than thirty fixes land across Oracle, PostgreSQL, MySQL and MariaDB, diff-changelog, and embedded use. Thanks to the twenty-five contributors who made their first contribution in this release!

Latest release: v5.0.4, August 20, 2026

Nightly builds: Updated automatically after each successful test run on main: https://github.com/liquibase/liquibase/releases/tag/nightly

Roadmap: https://github.com/orgs/liquibase/projects/3/views/9?layout=board

Thank you to every contributor who filed an issue, reviewed code, or submitted a fix.

New Contributors

  • @v-petrovych made their first contribution in #7729

  • @mgustimz made their first contribution in #7696

  • @Khromushkin made their first contribution in #7759

  • @officialasishkumar made their first contribution in #7705

  • @jstastny made their first contribution in #7566

  • @pranithreddym made their first contribution in #7708

  • @edu1006 made their first contribution in #7847

  • @profiluefter made their first contribution in #7560

  • @Devansh-ops made their first contribution in #7834

  • @singhvishalkr made their first contribution in #7725

  • @Akshatsharma2205 made their first contribution in #7798

  • @renechoi made their first contribution in #7869

  • @om7057 made their first contribution in #7825

  • @yosimasu made their first contribution in #7831

  • @makssent made their first contribution in #7850

  • @felhag made their first contribution in #7803

  • @hutiefang76 made their first contribution in #7835

  • @sakshichitnis27 made their first contribution in #7866

  • @seonwooj0810 made their first contribution in #7812

  • @kamilkrzywanski made their first contribution in #7848

  • @fudianchn made their first contribution in #7881

  • @ET-TOUNANI made their first contribution in #7801

  • @pwielgolaski made their first contribution in #7909

What's in this release

Security

Release signing key rotated: The Liquibase release signing key was rotated following the revocation of the old key. GPG verification requires Liquibase Community 5.0.4 or later. Past releases are safe and untampered.

(#7737, #7738, #7740, #7756, #7739, #7746) by @v-petrovych Database credentials are now redacted from error messages, logs, and stored parameters. If you pass a JDBC URL with embedded credentials (such as jdbc:postgresql://user:password@host/db) and the connection fails, the password no longer appears in the resulting error message or in log output. This covers unrecognized-driver errors and mistyped offline: URLs, and the URL sanitizer now handles PostgreSQL, SQL Server, and third-party JDBC drivers rather than only a few specific databases. Credential-bearing values captured from CLI arguments, liquibase.properties defaults files, and Maven plugin parameters are now masked before being stored internally. The init start-h2 example server also now prints ***** instead of the real password in its connection information banner. If you relied on copying the password from that output, use the value you configured instead (the default is letmein).

(#7741, #7742, #7743, #7764) by @v-petrovych Passwords are cleared from memory once Liquibase is done with them. The CLI, the Maven plugin, and the Ant tasks now release password values as soon as the operation that needed them finishes. For Ant, clearing happens when the build finishes, so a shared <database> definition keeps working across multiple tasks in one build. This shortens the window in which a heap dump or memory inspection of a long-running JVM could expose your credentials. If you call Liquibase programmatically, CommandScope.execute() does not clear credentials automatically (so reusing one scope for several executions keeps working), and you can opt in by calling the now-public clearCredentialArguments() after your last execution.

(#7747, #7748, #7768, #7767, #7766, #7765, #7750) by @v-petrovych New opt-in lockdown flags for changelog features that can execute code or reach outside your project. Several changelog features are intentionally powerful under Liquibase's standard trust model of team-authored, team-reviewed changelogs. If you run changelogs from less-trusted sources, you can now disable each of these features individually. Every flag defaults to true, so nothing changes unless you set it. When a flag is set to false, the offending element is rejected with a clear error that names the flag, before any of its code runs.

  • Set liquibase.allowExecuteCommand=false to reject the executeCommand change, which runs OS shell commands.

  • Set liquibase.allowCustomChange=false to reject both customChange and customPrecondition, which load and run Java classes named in the changelog.

  • Set liquibase.allowSqlPrecondition=false to reject the sqlCheck precondition, which executes raw SQL during precondition evaluation.

  • Set liquibase.allowIncludeAllClasses=false to reject the resourceFilter and resourceComparator attributes of includeAll, which also load Java classes by name.

  • Set liquibase.allowExternalChangelogPaths=false to reject classpath: and absolute filesystem paths in include, includeAll, and sqlFile. Paths using relativeToChangelogFile="true" are always allowed, and the default keeps Spring Boot's classpath:db/changelog/... pattern working unchanged.

  • Set liquibase.allowParentDirectoryReferences=false to enforce strict path containment, rejecting .. path segments and symlinks that resolve outside your configured search path. The default of true preserves existing layouts that reference files one level up. A future major release plans to flip this default to false, so consider testing with the strict setting now.

(#7729) by @v-petrovych Changelog XML no longer accepts DOCTYPE declarations under secure parsing. With liquibase.secureParsing=true (the default), an XML changelog that carries a <!DOCTYPE ...> preamble is now rejected, closing an avenue for XML entity tricks to read files through the changelog parser. Liquibase changelogs validate against XSD, not DTD, so standard changelogs are unaffected. If a third-party changelog fails with a DOCTYPE error, remove the DOCTYPE preamble from the file.

(#7892) by @SvampX Fixed a reflected cross-site scripting vulnerability in the status servlet. The embedded status page previously wrote the request URL and logged messages into its HTML without escaping, so a crafted link could inject script into the page. The log-level links are now relative, and log messages and stack traces are HTML-escaped, which also fixes messages containing markup being rendered instead of displayed as text. The same change fixes several cases where Liquibase reported a NullPointerException instead of a meaningful error.

Notable improvements

(#7650) by @filipelautert with @Copilot Opt in to strict context matching with the new nocontexts pseudo-context. Changesets that declare a context expression have always run even when no --contexts value is supplied at runtime, which many users find surprising. You can now write context="!nocontexts AND mycontext" to make a changeset run only when a runtime context is actually provided, or context="nocontexts OR fallback" to target runs where no contexts were given. Existing changelogs are unaffected unless they use nocontexts as a real context name, which is now a reserved word.

(#7759, #7827) by @Khromushkin and @KushnirykOleh PostgreSQL: partitioned tables keep their PARTITION BY clause. generate-changelog and diff-changelog silently dropped the partition strategy from declaratively partitioned tables, so applying the generated changelog produced a plain, non-partitioned copy of the source table. The partition definition is now captured in a new partitionBy attribute on createTable (for example partitionBy="RANGE (created_at)"), carried through diffs, and applied on update. You can also write the attribute in handwritten changelogs. The catalog query behind this is skipped on Amazon Redshift and other engines older than PostgreSQL 10, which do not have the needed system tables.

(#7841) by @SvampX Configure the reference connection separately in diff and diff-changelog. Two-connection commands previously resolved every configuration setting identically for the target and reference connections. Configuration settings that opt in can now be given a different value for the reference connection through a <namespace>.reference.<suffix> sibling key. When the sibling key is unset, the reference connection inherits the primary value, so existing setups behave exactly as before. This primarily enables database extensions to support different authentication methods for the target and reference databases. See Use different authentication for the reference database for the documentation.

(#7522) by @Vampire New no-argument listUnexpectedChangeSets() method. When embedding Liquibase in Java, the Liquibase class now offers listUnexpectedChangeSets() with no arguments, consistent with methods like validate() and update().

(#7850) by @makssent Firebird: RedDatabase servers are now recognized. RedDatabase, a Firebird-compatible fork, reports its own product name, so Liquibase treated it as an unsupported database and generated generic SQL that the server rejected when creating the tracking tables. Liquibase now detects RedDatabase as Firebird, so update and other commands work out of the box. Existing changelogs with dbms="firebird" apply as-is.

(#7847) by @edu1006 Extension authors: LockService methods can now throw any LiquibaseException. The throws clauses of LockService.init(), destroy(), and forceReleaseLock() were widened from DatabaseException to LiquibaseException, so custom lock services can propagate typed exceptions without wrapping them. The change is binary compatible and existing implementations compile unchanged. Only callers that catch exactly DatabaseException from these methods need to widen their catch block.

(#7900) by @SvampX The CLI banner now points to Liquibase Secure. The two informational lines in the startup banner now read "Taking Liquibase to production?" with a link to liquibase.com/liquibase-secure, replacing the documentation and training links.

Fixes

(#7876, #7877, #7909) by @filipelautert and @pwielgolaski Running Liquibase concurrently in one Java process is now reliable. Several long-standing thread-safety problems are fixed. Threads that never inherited Liquibase's initialization state, such as pool threads created before Liquibase started, could fail with "not a subtype" service-loading errors. Parallel command executions shared internal command state and configuration values, so one execution could pick up another's arguments. And multi-module Maven builds run in parallel with -T could read a sibling module's changelog state or fail with Cannot execute commands against an offline database. Multithreaded embedders, parallel executions, and parallel Maven reactors now each keep their own state.

(#7825) by @om7057 Log context data is cleaned up per thread. When embedding Liquibase in a multithreaded application, structured logging data added by one thread could be wiped when another thread exited a shared scope, leaving stale values permanently attached to the logging context. Each thread now tracks and cleans up its own entries.

(#7869) by @renechoi Checksums are always calculated for the requested checksum version. A changeset cached its first generated checksum and returned it for every later request, even when a different checksum version was asked for, so the returned checksum could carry the wrong version number with no error or log message. Requesting a checksum for a specific version now recalculates when the cached value was built for a different version.

(#7896) by @hutiefang76 An update after a rollback in the same process is no longer skipped. Liquibase caches a successful up-to-date check for the lifetime of the Java process. After a rollback, that stale cached result could make the next update report the database as already up to date and skip the rolled-back changesets. The cache is now invalidated whenever a rollback command finishes.

(#7886) by @om7057 No more spurious "could not release lock" messages from update commands. Commands such as update-count, update-sql, and update-to-tag attempted to release the changelog lock even though the command pipeline had acquired it, so the lock was released twice and Liquibase printed a "could not release lock" message. The lock is now released only by the code that acquired it.

(#7704) by @officialasishkumar includeAll no longer fails when older tracking data lacks a stored changelog path. When tracking data was written by an older Liquibase version or an extension that does not record a separate stored changelog path (reported with the MongoDB extension when using logicalFilePath), comparing file paths failed with a NullPointerException. Liquibase now falls back to the changelog path when the stored path is missing.

(#7696) by @mgustimz dbms-filtered changelog properties resolve correctly with user-provided changelog parameters. When embedding Liquibase and passing your own ChangeLogParameters through CommandScope, the database filter was never set, so properties restricted to other databases via dbms matched incorrectly and the fallback property without a dbms filter was skipped. The filter is now set from the actual database.

(#7697) by @mgustimz No more NullPointerException when a JDBC driver returns no URL. Some drivers, such as the IBM Informix 15.0.1.0 driver, return null from DatabaseMetaData.getURL(). Database detection then crashed with a NullPointerException, even when connecting to unrelated databases. A null URL is now handled safely.

(#7643) by @filipelautert Sequence attributes are validated as integers by the XSD. The startValue, incrementBy, maxValue, minValue, and cacheSize attributes on sequence changes accepted any string during XML validation and then failed at runtime with NumberFormatException. They now validate as integers or ${property} expressions, so mistakes are caught early with a clear validation error naming the attribute.

(#7798) by @Akshatsharma2205 Grouped context expressions with commas are evaluated correctly. A context expression such as (a,b,c) AND a was split on every comma into invalid fragments, so an unrelated runtime context like b could incorrectly match. Commas inside parentheses are no longer treated as top-level separators, while top-level comma lists behave as before.

(#7831) by @yosimasu --output-default-schema and --output-default-catalog are honored by SQL output commands. Setting either flag to false on update-sql, rollback-sql, changelog-sync-sql, future-rollback-sql, and the other SQL output commands had no effect, and generated SQL always qualified objects in the default schema. The flags now work as documented, so objects in the default schema are left unqualified when you set them to false.

(#7835) by @hutiefang76 Formatted SQL preconditions display your custom failure messages. In formatted SQL changelogs, the onFailMessage and onErrorMessage attributes were recognized but never applied, so precondition failures always showed the generated default message. Your custom messages now appear, including messages containing spaces and apostrophes.

(#7866) by @sakshichitnis27 The database URL can come from --driver-properties-file. As documented, JDBC connection properties including url can be supplied through --driver-properties-file, but commands still failed with Invalid argument '--url': missing required argument unless a URL was also configured elsewhere. A url in the driver properties file now satisfies the requirement. An explicitly configured URL still takes precedence.

(#7812) by @seonwooj0810 JDBC statements are closed during snapshots. Result sets obtained from database metadata calls during snapshot operations were closed without closing the statement that produced them, leaking JDBC statements until the connection closed. Users of connection pools saw leak warnings. Both are now closed together.

(#7848) by @kamilkrzywanski Liquibase starts even when an extension has a missing optional dependency. On newer JDKs, a single extension service referencing a missing class (for example the liquibase-hibernate Spring integration without Spring on the classpath) could abort service discovery entirely and stop Liquibase from starting. The broken provider is now logged and skipped, and the remaining services load normally.

(#7845) by @SvampX Extensions with the same priority no longer silently replace each other. When two configuration value modifiers or table-name generators declared the same order value, only one was kept and the rest were silently discarded. With a secrets-manager extension this could mean a vault reference reached the database as a literal string, with no error. Same-order plugins now coexist and all run, in a deterministic order.

(#7882) by @fudianchn customChange parameters with acronym names work again. Since 4.29.0, param elements whose name is an uppercase acronym such as URL or GET were silently dropped instead of being passed to the matching setURL-style setter. Acronym property names are now resolved following the JavaBeans convention, restoring the earlier behavior. Regular camelCase parameters are unaffected.

(#7566) by @jstastny loadUpdateData handles text values with spaces and braces quietly. When a value destined for a large text column looked like a file path but contained spaces, Liquibase logged a misleading warning with a full stack trace even though the value was inserted correctly as a string, and values containing { or } failed with an IllegalArgumentException. The warning is now a debug-level message and such values load cleanly.

(#7884) by @wwillard7800 The deprecated AWS extension warning goes to stderr. The startup warning about deprecated standalone AWS extensions was printed to standard output, where it could pollute captured command output. It now goes to standard error, following the usual convention for diagnostics.

(#7910) by @wwillard7800 Changeset failures are reported once, not twice. A failing changeset was logged with its full stack trace and then rethrown and reported again, so the same error appeared twice, and under quiet logging configurations the stack trace still flooded the console. The failure is now reported once, with the full stack trace still available at verbose log levels.

(#7801, #7898) by @ET-TOUNANI and @filipelautert diff-changelog orders new columns before the foreign keys that need them. In some cases, reported on SQL Server, the generated changelog placed an addForeignKeyConstraint change before the addColumn change that creates the referenced column, so applying it failed. Column additions on existing tables now stay ahead of dependent foreign keys, while columns belonging to tables the same changelog creates correctly remain inside their createTable.

(#7881) by @fudianchn diff-changelog no longer fails when a changed index backs a primary key or unique constraint. Comparing databases whose primary key or unique constraint column order differs crashed with ClassCastException: Index cannot be cast to PrimaryKey. The comparison now completes, and the generated changelog still drops and recreates the constraint using each database's own constraint name.

(#7897) by @filipelautert Offline diff-changelog no longer duplicates primary key changes. When comparing offline snapshots of tables whose composite primary key differs only in column order, the generated changelog contained the dropPrimaryKey and addPrimaryKey pair twice. Offline comparisons now produce the same changesets as online ones.

(#7834) by @Devansh-ops Oracle: q-quoted string literals no longer break statement splitting. Oracle q and nq alternative-quoted literals containing embedded quotes or backslashes confused statement parsing in sqlFile changes, so standalone / delimiters were absorbed and multiple statements were sent to the database as a single statement. These literals are now recognized as single tokens, and statements split correctly.

(#7641) by @filipelautert Oracle: the current date-time function now uses the session time zone. Liquibase generated SYSTIMESTAMP for the current date-time function, which returns the time in the database server's operating system time zone. It now generates CURRENT_TIMESTAMP, which respects the session time zone, matching what changelog authors expect. If you relied on the operating system time zone behavior, generated timestamps will change.

(#7808) by @marchof Oracle: TIMESTAMP WITH TIME ZONE columns are captured correctly again. A regression caused snapshots to miss the time zone qualifier on Oracle columns reported as TIMESTAMP(6) WITH TIME ZONE(13), so generated changelogs lost the time zone information and could emit invalid syntax. The qualifier is now detected and emitted correctly.

(#7645) by @filipelautert Oracle: the primaryKeyExists precondition respects the primary key name. Checking for a primary key by name on Oracle matched any primary key on the table, so the precondition passed even when the named key did not exist. The name is now checked.

(#7803) by @felhag PostgreSQL: defaultSchemaName applies to changesets with runInTransaction="false". Liquibase set the default schema using SET LOCAL SEARCH_PATH, which only lasts for the current transaction, so changesets running outside a transaction ignored the configured default schema. LOCAL is now used only when the changeset runs inside a transaction.

(#7705) by @officialasishkumar MySQL: timestamp definitions keep their column attributes. Native timestamp column definitions that included attributes such as NOT NULL, DEFAULT CURRENT_TIMESTAMP, or ON UPDATE CURRENT_TIMESTAMP had those attributes dropped during type normalization. They are now preserved, while plain timestamp and precision-only forms continue to normalize as before.

(#7708) by @pranithreddym MySQL: boolean columns map to TINYINT(1) instead of BIT(1). The MySQL driver reports TINYINT(1) columns as BIT, so snapshots wrote BIT(1) into changelogs, fresh deployments created the wrong column type, and every later generate-changelog reported a spurious diff. Boolean types on MySQL now always emit TINYINT(1), the canonical MySQL boolean representation. Use the explicit bit type if you genuinely need BIT(n). MariaDB behavior is unchanged. See What support does Liquibase have for MySQL? for the full type-handling history.

(#7725) by @singhvishalkr MySQL/MariaDB: enum columns with DEFAULT NULL no longer produce defaultValue="null". Running generate-changelog on an enum column with DEFAULT NULL emitted the literal string "null" as the default value. The driver's "NULL" string is now treated as an actual null, matching the existing Oracle and DB2 handling, so no defaultValue attribute is emitted.

(#7560) by @profiluefter MySQL/MariaDB: the reserved keyword lists are up to date. Reserved words missing from the lists, such as PARALLEL and, on MariaDB, OFFSET, were not quoted in generated SQL, causing errors when object names collided with them. The lists now match the current MySQL and MariaDB documentation, including per-version differences, and words that are not actually reserved are no longer quoted unnecessarily.

Security, driver and other updates

PR

Description

#7718

chore(deps): Bump org.slf4j:slf4j-jdk14 from 2.0.17 to 2.0.18

#7732

chore(deps): Bump targetMavenVersion from 3.9.15 to 3.9.16

#7734

chore(deps): Bump test-deps group with 4 updates

#7731

chore(deps-dev): Bump maven-enforcer-plugin from 3.6.2 to 3.6.3

#7773

chore(deps): Bump build-tools group with 3 updates

#7772

chore(deps-dev): Bump production-deps group with 2 updates

#7762

chore(deps): Bump github-actions group with 7 updates

#7787

chore(deps): Bump github-actions group with 2 updates

#7790

chore(deps): Bump spring.version from 7.0.7 to 7.0.8

#7802

chore(deps-dev): Bump build-tools group with 5 updates

#7797

chore(deps): Bump alpine from 3.23 to 3.24 in /docker

#7792

Update LPM to v0.3.5

#7818

chore(deps): Bump junit-jupiter.version from 6.1.0 to 6.1.1

#7817

chore(deps): Bump test-deps group with 4 updates

#7819

chore(deps): Bump github-actions group with 8 updates

#7805

chore(deps-dev): Bump org.apache.ant:ant-antunit from 1.4.1 to 1.5.0

#7837

chore(deps): Bump junit-jupiter.version from 6.1.1 to 6.1.2

#7839

chore(deps): Bump test-deps group with 4 updates

#7844

chore(deps): Bump github-actions group with 11 updates

#7849

chore(deps): Bump build-tools group with 3 updates

#7867

chore(deps): Bump flatten-maven-plugin from 1.7.3 to 1.8.0

#7865

chore(deps-dev): Bump maven-bundle-plugin from 6.0.2 to 6.1.0

#7855

chore(deps): Bump github-actions group with 6 updates

#7853

chore(deps-dev): Bump org.xerial:sqlite-jdbc from 3.53.2.0 to 3.53.2.1

#7872

chore(deps-dev): Bump mariadb-java-client from 3.5.9 to 3.5.10

#7871

chore(deps): Bump test-deps group with 2 updates

#7873

chore(deps-dev): Bump com.mysql:mysql-connector-j from 9.7.0 to 26.7.0

#7878

chore(deps-dev): Bump org.objenesis:objenesis from 3.5 to 3.6

#7874

chore(deps): Bump github-actions group with 6 updates

#7854

chore(deps): Bump gmavenplus-plugin from 4.3.1 to 5.1.0

#7915

chore(deps): Bump maven-wrapper from 3.2.0 to 3.3.4

#7914

chore(deps): Bump apache-maven from 3.9.5 to 3.9.16

#7890

chore(deps): Bump test-deps group with 4 updates

#7889

chore(deps): Bump commons-collections4 from 4.5.0 to 4.6.0

#7880

chore(deps-dev): Bump ojdbc8 from 19.31.0.0 to 19.32.0.0

#7887

chore(deps): Bump github-actions group with 4 updates

#7879

Update LPM to v0.3.6