• Concept
  • Version ยท 6.0
  • Create

Upgrading to Liquibase Secure 6.0

Last updated: September 29, 2026

Liquibase Secure 6.0 introduces user-visible breaking changes to the command line interface. This guide explains what changed and how to migrate your scripts and CI pipelines.

What you need to do

Liquibase Secure 6.0 changes several things that affect automated use more than interactive use. Find the entries below that describe your setup, read those sections, then follow Upgrade by install method for the steps for the way you installed Liquibase.

  • If your scripts or CI pipelines capture command output, read STDOUT and STDERR routing and Migrate your scripts. In 6.0, update, rollback, and db-doc write everything to STDERR, so a script that captures STDOUT from those commands gets nothing. Look-up commands such as status, history, and validate still write their results to STDOUT.

  • If a log collector reads Liquibase log records from the console rather than from a log file, read Console messages in the log. Console messages such as the banner are no longer mirrored into the log unless you set --log-file.

  • If your pipeline branches on the exit code, read Error handling and Policy check severity and exit codes. Treat any nonzero exit code as a failure instead of testing for 1.

  • If you run Liquibase inside your own application, read Embedded and library integrations and Host logging configuration. Call setOutput() to keep command results, and expect Liquibase to leave your application's own logging configuration alone.

  • If you maintain a custom extension that supports Amazon Redshift, read Custom extensions that detect Amazon Redshift. A check for instanceof PostgresDatabase no longer matches Redshift, and nothing reports that it stopped matching.

  • If you have runOnChange changesets that use <sqlFile dbms="...">, read Changeset checksums before you upgrade. Correcting a stored checksum runs that changeset one more time.

  • If you write formatted SQL changelogs, read Formatted SQL boolean attributes. A misspelled boolean value such as runAlways:banana now stops the parse instead of being treated as false.

  • If you run Db2 for z/OS, read Db2 for z/OS tracking-table placement and remove the tablespace and index properties from your configuration.

One thing needs no action from you. Your existing Liquibase Secure 5.x license key works with 6.0.

STDOUT and STDERR routing

What changed

In Liquibase 5.x and earlier, the CLI wrote command results and diagnostic messages to the same stream. There was no separation between machine-readable output and human-readable text, which made piping unreliable. Running liquibase update-sql | jq sent the banner and progress lines into jq along with the SQL.

In 6.0, Liquibase separates its output into two planes:

Plane

Destination

What it carries

Results

STDOUT

Command results: SQL, JSON, and diff output, plus the text results of look-up commands such as status, history, validate, and checks run. When you set --output-file, this plane goes to the file instead.

Human and diagnostics

STDERR

Banner, progress messages, success confirmations, warnings, and errors.

Help and version

STDOUT

Output from --help and --version, so that command substitution and piping work without redirection. Argument parse errors go to STDERR.

What each command writes

Command

STDOUT

STDERR

update-sql, rollback-sql, future-rollback-sql

Generated SQL

Banner, progress, success message

snapshot (JSON format), diff (with --format)

JSON or diff payload

Banner, progress, success message

update, rollback, db-doc

Empty

Banner, progress, summary or report, success message

status, history

Status text or deployment history table

Banner, progress, success message

validate

No validation errors found. on success, or Validation Failed: and the list of problems on failure

Banner, progress, and on failure ERROR [LB-CHG-0035] Changelog validation failed, which repeats the problem list and adds a suggested fix

checks run

Check results and findings

Banner, progress, success message

--help, --version

Help or version text

Argument parse errors only

Any other command that fails

Empty

Banner, progress, error message. Stack trace only at verbose level

Note: status, history, validate, and checks run write their results to STDOUT, but that text is formatted for people to read rather than for parsing. Treat its layout as unstable and avoid building scripts that depend on the column positions.

A failing validate writes the list of problems to STDOUT as the command result and exits with a nonzero code. STDERR carries the coded ERROR [LB-CHG-0035] Changelog validation failed line, which repeats the same list and adds a suggested fix, so a script that captures both streams sees the problems twice.

Examples

loading

Are you affected?

You are affected if your scripts or CI pipelines capture STDOUT from update, rollback, or db-doc. These commands have no machine-readable result, so in 6.0 they write everything to STDERR and leave STDOUT empty.

You are not affected if you only capture the structured output of commands like update-sql, snapshot, or diff, which was already on STDOUT. You are also unaffected if you do not parse Liquibase output streams at all.

status, history, validate, and checks run write their results to STDOUT in 6.0, so a pipeline that captures STDOUT from those commands still receives their output.

Migrate your scripts

Choose one of the following approaches.

Read from the correct plane

For a command with a results payload, read STDOUT and let STDERR go to your terminal or a log file. For a command with no results payload, redirect STDERR if you want to capture the human-readable output.

loading

Set the compatibility flag temporarily

Setting liquibase.legacyStreamRouting to true restores the 5.x behavior of routing everything to STDOUT. This is a temporary measure while you update your scripts. The warning Liquibase prints names the release in which the flag is removed.

loading

The legacy-stream-routing compatibility flag

Setting

Value

Property

liquibase.legacyStreamRouting

Environment variable

LIQUIBASE_LEGACY_STREAM_ROUTING

CLI argument

--legacy-stream-routing=true

Default

false

Scope

Global (applies to the whole process)

Precedence follows the standard Liquibase chain: the CLI argument wins over the environment variable, which wins over the defaults file.

When you set the flag to true, Liquibase writes a one-time deprecation warning to STDERR on the first message it produces:

loading

The flag is a bridge, not a permanent setting. It is removed in a future release, and the warning Liquibase prints names which one, so update your scripts to expect diagnostics on STDERR rather than leaving the flag switched on.

What the contract does not cover

The legacy Main entrypoint, which predates the current CLI, writes directly to STDOUT and STDERR and is not covered by the 6.0 contract or by the compatibility flag. Do not rely on the stream split when you invoke it.

Console messages in the log

Liquibase Secure 6.0 changes when console messages, such as the banner, each changeset as it runs, and the success message, are also written to the log as [liquibase.ui] records. In Liquibase Secure 5.2 and earlier, --mirror-console-messages-to-log defaulted to true, so Liquibase always mirrored them. In 6.0, Liquibase mirrors them by default only when you set --log-file.

You are affected if you run with --log-level=INFO or more detailed and no --log-file, and something reads the log records Liquibase writes to the console. For example, a log collector that parses --log-format=JSON output from STDERR no longer receives [liquibase.ui] records. To keep the 5.x behavior, set --mirror-console-messages-to-log=true.

In plain text output, the same change means each console message appears once instead of twice, so there is nothing to do unless you relied on the timestamped copy.

You are not affected if you write the log to a file with --log-file, because Liquibase still mirrors console messages into the file. You are also not affected if you already set --mirror-console-messages-to-log explicitly, because an explicit value behaves as it did in 5.x.

Embedded and library integrations

The stream contract above describes the Liquibase CLI. Liquibase 6.0 also changes where command output goes when you run Liquibase inside your own application.

What changed

In 5.x and earlier, Liquibase wrote command results to System.out whenever the caller did not choose a destination. That happened even when Liquibase ran inside a host application, so a Spring Boot service that ran update-sql had the generated SQL printed to the application's console without asking for it.

In 6.0, Liquibase resolves the default destination from the surrounding context. In an embedded context, and only when the caller has not called setOutput(), Liquibase discards command results instead of writing them to System.out. This applies to the results payload only. It does not change your application's logging configuration, and it does not change what Liquibase logs.

Are you affected?

Liquibase treats a context as embedded when the integration name is absent, which is the case for a CommandScope you build yourself, or when the integration name is spring or maven. Every other context keeps System.out.

Context

Where command results go in 6.0

Liquibase CLI

STDOUT. Unchanged.

Gradle, Ant, and other CLI wrappers

STDOUT. Unchanged.

Liquibase Maven goals

Your build console. The goals set the destination explicitly, either directly or through the Liquibase class.

The Liquibase class

The writer you pass to it. Its methods set the destination explicitly.

Spring Boot

Discarded, unless the caller calls setOutput().

A CommandScope you build yourself

Discarded, unless you call setOutput().

Only the results payload is affected. Commands that produce no payload, such as update, have nothing to discard.

Keep the output

Call setOutput() on the command scope before you execute it. This works the same way in every context, including the CLI.

loading

How to tell that output is being discarded

The first time a command writes to the discarded stream, Liquibase logs one message at INFO level:

INFO: Command output not explicitly configured; output will be discarded
in this embedded context. Call CommandScope.setOutput() to direct output
to the desired destination.

The message appears once per command. You see it only when Liquibase logging is enabled at INFO level or finer for liquibase.command.ProCommandOutputService.

Interactive prompts

In 5.x, calling LoggerUIService.setAllowPrompt(true) threw an IllegalArgumentException. Embedded callers hit this when framework code tried to turn on prompting even though LoggerUIService cannot prompt. In 6.0 the call logs a message and does nothing, and prompt() keeps returning default values, so startup no longer fails for that reason.

Host logging configuration

Liquibase Secure 6.0 replaces its Java-util-logging backend with an SLF4J-backed one. The change matters most when you embed Liquibase, because earlier releases reconfigured the logging setup of the application they ran inside.

In 6.0, Liquibase configures only a logging context that Liquibase created and owns. It no longer resets your application's logger context, and it no longer adds or removes handlers on your application's root logger.

  • Liquibase Secure, embedded: your application keeps its own log levels and handlers. This is the change.

  • Liquibase Secure, CLI: no change. --log-level and --log-file behave as before.

  • Liquibase Community: no change. The Java-util-logging path is untouched.

  • Extension authors who implement Logger or LogService: no change. No interface changed in this release.

Liquibase also ships logback-classic as a provided dependency, so embedding Liquibase does not add a second SLF4J binding to an application that already supplies one.

Note: When Liquibase does not own the logging context, --log-format=json has no effect unless you wire Liquibase's JSON encoder into your application's own logging configuration. Otherwise Liquibase logs in your application's format, and it prints no warning. The Maven plugin is not affected: <logFormat>JSON</logFormat> produces structured JSON, as --log-format does on the CLI.

Custom extensions that detect Amazon Redshift

In Liquibase Secure 6.0, RedshiftDatabase extends AbstractPostgresDatabase instead of PostgresDatabase. AbstractPostgresDatabase is the new parent class for the whole PostgreSQL family. PostgresDatabase now covers PostgreSQL and the databases built on it, such as CockroachDB and EDB Postgres Advanced Server. Amazon Redshift is no longer one of them.

You are affected if you maintain a custom extension that uses instanceof PostgresDatabase to include Amazon Redshift. That check returns false for Redshift in 6.0, and nothing reports it. A generator whose supports() method now returns false, or whose getPriority() method now returns PRIORITY_NONE, is skipped for Redshift, and Liquibase uses the next implementation that matches. The command still succeeds, so the only sign is SQL or snapshot output that differs from what 5.x produced.

To fix it, check instanceof AbstractPostgresDatabase when the behavior applies to the whole PostgreSQL family, including Redshift. Check instanceof RedshiftDatabase when it applies to Redshift alone.

You are not affected if your extension checks instanceof PostgresDatabase only for behavior specific to PostgreSQL. Those checks match PostgreSQL, CockroachDB, and EDB Postgres Advanced Server exactly as they did in 5.x. Connections to PostgreSQL itself are unchanged.

Error handling

Liquibase Secure 6.0 changes how failures are reported. Most failures now print a stable LB- error code and a suggested next step on STDERR, and the process exit code is no longer always 1.

You are affected if your scripts or alerting rules match on Liquibase error text. Anchoring on Unexpected error running Liquibase: no longer catches a failure that carries a code. The same applies to the pre-6.0 Exception Primary Class, Reason, and Source lines, which the coded format replaces. Match on the message body, or on the LB- code, which is a stable identifier.

You are affected if your pipeline branches on exit code 1 to mean any Liquibase failure. Treat any nonzero exit code as a failure instead. The exit code for a given failure depends on how far that failure has moved to the new error model, and which failures use a more specific code changes from release to release, so do not test for particular values.

You are not affected if you drive Liquibase through Maven or Spring. The Maven goal fails the build by throwing and Spring propagates the exception. Neither consults the CLI exit map.

For the full list of error codes, and the order in which Liquibase decides an exit code, see Error codes and exit codes.

Policy check severity and exit codes

Liquibase Secure 6.0 adds a second place to set a policy check's severity. Severity decides the exit code a check returns when it fires. The levels and their codes are unchanged: INFO returns 0, MINOR returns 1, MAJOR returns 2, CRITICAL returns 3, and BLOCKER returns 4. Anything above INFO returns a nonzero code, so it can stop a job.

You are affected if your pipeline branches on a policy check's exit code. In 6.0 a check's severity can also be set in the web application, under Govern > Policies. Selecting Manage on your catalog opens Policy Checks Packages. Opening the package that holds the check and selecting See Current Configuration shows a Severity row with the current level and its exit code, for example INFO (0), and Edit changes it. Confirm where the checks your pipeline runs are managed, so you know which place sets the code your automation sees.

You are not affected if you manage checks in a checks settings file. That path is unchanged. Run liquibase checks customize --check-name=your_check_name and choose the level at the prompt, and the next checks run returns that exit code when the check fires.

Note: Checks in the Liquibase Default Catalog are read-only. Severity changes need a catalog your team owns.

For the full procedure in both places, see Set policy check severity and exit codes.

Policy check management moves to the web app

In Liquibase Secure 5.x you managed policy checks through a checks settings file and the checks commands. Liquibase Secure 6.0 adds central management in the web app, under Govern in the left navigation, which has two areas: Policies, and Assignments. Your policies are organized there, and the Liquibase CLI still enforces them on every run.

Your existing configuration keeps working. The checks commands are unchanged, so a pipeline that passes a checks settings file runs the same checks with the same severities and the same exit codes it did on 5.x. Moving management into the web app is optional, and you can do it after you upgrade.

To move management into the web app:

  • Browse the check library. Every workspace includes the read-only Liquibase Default Catalog, which holds the 60 checks built into Liquibase Secure 6.0, grouped into seven category packages.

  • Group the checks your organization enforces into your own catalog and packages, so they can be distributed together.

  • Assign your packages to your assets, then keep running policy checks from the CLI as you do today.

Import your existing policy checks walks through bringing a checks settings file into the web application. Before you change a severity in either place, see Policy check severity and exit codes.

Changeset checksums

Liquibase Secure 6.0 fixes an issue in how checksums are calculated for <sqlFile> changes that use the dbms attribute, such as <sqlFile dbms="mssql" ... />. The dbms attribute limits a change to one type of database, so Liquibase must know which database it is connected to before it can apply the change. In some cases, most often when you run liquibase flow, Liquibase calculated the checksum before it knew the target database, and stored the checksum of an empty string, 9:d41d8cd98f00b204e9800998ecf8427e, instead. Only Liquibase 4.25.1 through 5.2.0 stored these checksums.

You were affected if a runOnChange changeset has a stored checksum of 9:d41d8cd98f00b204e9800998ecf8427e. Because that checksum was based on an empty string rather than on the script, editing the script no longer changed the checksum, so Liquibase stopped deploying your script edits without reporting a warning or an error. Liquibase Secure 6.0 corrects the checksum, and the changeset detects script edits again.

You need to act before you upgrade if one of those runOnChange changesets also has SQL that is not safe to run more than once. Correcting the checksum runs the changeset one additional time, which for a plain INSERT means one duplicate row. Rewrite that SQL first so that running it twice has the same effect as running it once. SQL that is safe to repeat, such as MERGE or INSERT ... WHERE NOT EXISTS, needs no change.

Changesets that are not runOnChange are corrected silently and do not re-execute. Liquibase makes the correction while it validates the changelog, so it happens on the first status or validate command as well as under the update, rollback, and changelog-sync families of commands. The one additional run of a runOnChange changeset happens under update, which is the only one of these commands that executes changesets.

When a runOnChange changeset runs again for this reason, Liquibase names it in a warning:

loading

To find the changesets to look at before you upgrade, run the following query. The rows it returns are candidates, not confirmations. Liquibase checks each row before it corrects it, and leaves alone any row that legitimately holds this checksum, such as a changeset scoped to a database other than the one you are connected to.

loading

Formatted SQL boolean attributes

Liquibase Secure 6.0 rejects a formatted SQL changeset whose boolean attribute carries a value other than true or false. A typo such as runAlways:banana now fails the parse with LB-CHG-0017, which names the attribute and the value it could not read. In Liquibase Secure 5.2 and earlier the value was accepted and treated as false, so the attribute was turned off and nothing in the output said so.

You are affected if a formatted SQL changelog contains a misspelled boolean value. A changelog that deploys cleanly today fails on the first update after you upgrade, and the changeset it fails on is one nobody edited. The typo was always there and nothing reported it. Correct the value rather than deleting the attribute. Deleting it restores that attribute's default, which is not always false. The attributes this applies to are failOnError, relativeToChangelog, relativeToChangelogFile, rollbackSplitStatements, runAlways, runInTransaction, runOnChange, splitStatements, and stripComments.

You are not affected if your changelogs are XML, YAML, or JSON. This change is in the formatted SQL parser only. XML rejects an invalid boolean during schema validation and carries a different code, and YAML and JSON still accept an unreadable value and treat it as false.

You are also not affected by the global property attribute. An unreadable value on a --property line logs a warning naming the attribute and the value applied, and the parse continues.

For the full attribute list and the rule on values, see SQL changelog example.

SQL comments in deployed changesets

Liquibase Secure 6.0 preserves the comments in your SQL and stored procedure changesets. Earlier releases stripped them, so a procedure body reached the database without them. Nothing is required of you to get the new behavior, and nothing changes about which changesets run.

If you relied on the comments being removed, set --global-strip-comments to true to restore the earlier behavior for every changeset, or set stripComments on an individual changeset.

Note: A comment sitting on its own after the last statement of a changeset is not sent to the database as a statement. Liquibase leaves it out of what it executes, so a trailing comment cannot fail a deployment.

Db2 for z/OS tracking-table placement

Liquibase Secure 6.0 simplifies where the Db2 for z/OS tracking tables live. Liquibase now places DATABASECHANGELOG and DATABASECHANGELOGLOCK with IN DATABASE, which was already the documented recommendation, and lets Db2 create the enforcing unique index under its own generated name. Db2 for z/OS allows one table per tablespace, so a single shared tablespace value could only ever place the first tracking table.

To name the database that holds the tracking tables, use liquibase.db2z.trackingTables.location.database. This property is unchanged, and it is now the only setting you need for tracking-table placement.

If your configuration still sets liquibase.db2z.trackingTables.location.tablespace, liquibase.db2z.databasechangelog.index, or liquibase.db2z.databasechangeloglock.index, remove them. In a defaults file with strict=true, a leftover key stops the command before it runs. Without strict mode the key is ignored, and Liquibase logs a warning at --log-level=info or finer. These keys are no longer accepted as CLI flags.

Licensing

Your existing Liquibase Secure 5.x license key works with the Liquibase Secure 6.0 CLI. No key change is required to upgrade, and nothing about your license changes at the moment you upgrade.

If you also run the Liquibase platform, some Change Intelligence capabilities require a Change Intelligence license. In the web interface those capabilities appear with a lock icon and a note naming the license they need. Policy management in the governance module, and adding your targets, changesets, and pipelines to the platform, are covered by a valid Liquibase Secure license.

Note: Liquibase Secure requires a license key to run. There is no unlicensed or Community fallback mode, so keep your key available when you upgrade.

Upgrade by install method

Follow the guide for the way you installed Liquibase, then apply the breaking changes above.

Install method

Action required

Guide

Docker

Pull the 6.0 image tag and update your run command.

Upgrade to Liquibase Secure with Docker

Maven

Update the plugin version in your pom.xml.

Upgrade from Liquibase Community to Liquibase Secure with Maven

Debian or Ubuntu

Update the package from the Liquibase repository.

Upgrade Liquibase on Linux with Debian or Ubuntu

Red Hat or CentOS

Update the package from the Liquibase repository.

Upgrade Liquibase on Red Hat or CentOS

Note: after upgrading, run liquibase --version to confirm your installed version.