Migrating Your Database to PostgreSQL
Liferay DXP 2026.Q3+
Migrating a Liferay DXP database to PostgreSQL has two phases. First, export the database schema to SQL files from either Server Administration or the REST API. Then run the database migration importer tool (db_migration_importer.sh) to copy the schema and its data into an empty PostgreSQL database. Supported source databases are DB2, MariaDB, MySQL, Oracle, and SQL Server. PostgreSQL is the only supported target. Liferay DXP must be shut down for the import. Downtime starts with that shutdown and ends after you restart on PostgreSQL.
For releases before 2026.Q3, see the PostgreSQL migration Knowledge Base article.
Back up your database before migrating.
Exporting the Database Schema
Exporting the schema requires a system administrator (omniadmin) account and a Liferay DXP instance running on the source database.
-
Go to Control Panel → System → Server Administration.
-
Click the Database Migration tab.
-
Complete these fields:
Export Files Path: Enter the path of a writable directory on the server. Liferay DXP writes the SQL files and report there. This field is required.
Text Verification: Complete the CAPTCHA challenge. By default, this field is hidden after your first successful challenge in a session.

-
Click Export.
The result appears in a message at the top of the tab:
| Condition | Message |
|---|---|
| The export succeeds | The database schema was exported successfully to [path]. |
| The export path is not writable | Unable to export the database schema. Verify the export path is writable. |
| The text verification entry is wrong | Text verification failed. |
| The CAPTCHA engine is misconfigured | A CAPTCHA error occurred. Please contact an administrator. |
The export writes to the Liferay DXP server’s file system rather than to your workstation. Confirm that tables.sql, indexes.sql, and db_migration_schema_export_report.txt exist in the directory named in the success message. A directory that doesn’t exist yet is created rather than rejected, so a mistyped path succeeds into a new directory.
Exporting the Schema with the REST API
To export the schema from a script instead of the UI, call POST /o/admin-server/v1.0/database-schema-export with the export path in the request body. exportFilesPath is a directory on the Liferay DXP server. The caller must be a system administrator. Unlike the UI, the REST API has no text verification challenge. Replace test@liferay.com:test with your own administrative credentials. Never use default credentials in production.
curl -X POST 'http://localhost:8080/o/admin-server/v1.0/database-schema-export' \
-H 'Content-Type: application/json' \
-u 'test@liferay.com:test' \
-d '{
"exportFilesPath": "/directory/"
}'
A successful call returns 200 with the export path, the names of the files written, and the name of the report file:
{
"exportFilesPath": "/directory",
"fileNames": [
"indexes.sql",
"tables.sql"
],
"reportFileName": "db_migration_schema_export_report.txt"
}
exportFilesPath in the response shows where the files were written, which may not match the path you sent.
| Status Code | Cause |
|---|---|
400 | Blank exportFilesPath value or a path the server cannot write to |
403 | Caller without system administrator permissions |
To browse the full OpenAPI specification and run requests interactively, open the API Explorer at http://[host]:[port]/o/api.
Reviewing the Exported Files
The export writes table definitions to tables.sql, index definitions to indexes.sql, and a report to db_migration_schema_export_report.txt. These files contain schema definitions only. The importer reads table data from the source database when it runs, so writes made between the export and the shutdown are included.
When database partitioning is enabled, the default instance keeps the plain tables.sql and indexes.sql names, and every non-default instance gets its own pair of files prefixed with its company ID: [companyId]_tables.sql and [companyId]_indexes.sql.
db_migration_schema_export_report.txt lists the tables the export did not write to tables.sql. These tables and their data are absent from PostgreSQL after the migration. To keep a table instead, copy the original files first, then add its definition to tables.sql and its indexes to indexes.sql, matching the columns of the source table.
Hand-editing the exported SQL files risks a failed or incomplete import. The files use PostgreSQL syntax, not your source database’s.
Importing into PostgreSQL
The importer tool requires a Java runtime on its path and Linux or macOS. Liferay DXP includes it at [Liferay Home]/tools/portal-tools-db-migration-importer (see Liferay Home). Run it from a host that can reach both the source database and the target PostgreSQL database. The --source-user account must have read access to every partition in the source database.
-
Create an empty PostgreSQL database. The target user must be able to create schemas and tables.
-
For a DB2, Oracle, or SQL Server source, copy the source database vendor’s JDBC driver into
[Liferay Home]/tools/portal-tools-db-migration-importer/lib. The tool includes the MariaDB, MySQL, and PostgreSQL drivers. -
Export the schema again if it changed since your last export, such as after a patch, a module deployment, or a new virtual instance. Liferay DXP must still be running.
-
Copy the exported SQL files to the host running the importer if they aren’t already there.
-
Shut down every Liferay DXP node connected to the source database. Nothing can write to the database while the importer reads from it.
-
From
[Liferay Home]/tools/portal-tools-db-migration-importer, rundb_migration_importer.shwith the path of the exported SQL files and the source and target connection settings:./db_migration_importer.sh \ --path "/directory/" \ --source-jdbc-url "jdbc:mysql://localhost:3306/schema" \ --source-user "xyz123" \ --source-password "xyz123" \ --target-jdbc-url "jdbc:postgresql://localhost:5432/schema" \ --target-user "xyz321" \ --target-password "xyz321"Keep all exported files in the
--pathdirectory, since one run imports every instance. The tool stops before it writes anything to the target database iftables.sqlorindexes.sqlis absent. Run the command from a restricted account or clear your shell history afterward, because the passwords appear in the history and the process list. -
Confirm the import succeeded. Open
db_migration_import_report.txtin the--pathdirectory and check that the source and target table and view counts match. Themissing target tablesandmissing target viewslists should be empty. When database partitioning is enabled, the report repeats these lines for every partition.ImportantExit code
0means the run finished without an exception, not that every table was copied. -
Back up the PostgreSQL database. This backup is your restore point if the first startup fails.
-
On every node, point Liferay DXP at the PostgreSQL database. See Configuring a Data Source for the available JDBC configuration methods. If your installation uses a JNDI data source, update that resource instead.
Remove any
hibernate.dialect,custom.sql.function.isnull, orcustom.sql.function.isnotnulloverrides set for the source database in an override file such asportal-ext.properties, or asLIFERAY_*environment variables. Liferay DXP detects the database from the JDBC connection and sets the dialect and these SQL functions itself. -
Start Liferay DXP.
-
Sign in as an administrator and confirm your sites and content are present, with no database errors in the startup log.
If you run the importer from a script, check the exit code as well as the report. The tool exits 0 on success, 1 on a failed import, and 2 whenever it prints the usage message. Exit 2 covers --help, a missing or invalid option, and an unsupported source or target database. A failed import writes a stack trace to standard error, and a successful one writes db_migration_import_report.txt.
Importer Options
These options are available for db_migration_importer.sh:
| Option | Required | Description |
|---|---|---|
--path <arg> | Yes | Path of the source SQL files |
--source-jdbc-url <arg> | Yes | JDBC URL of the source database |
--source-user <arg> | Yes | User name for the source database |
--source-password <arg> | Yes | Password for the source database user |
--target-jdbc-url <arg> | Yes | JDBC URL of the target PostgreSQL database |
--target-user <arg> | Yes | User name for the target PostgreSQL database |
--target-password <arg> | Yes | Password for the target PostgreSQL database user |
--jdbc-batch-size <arg> | No | JDBC batch size (default 2500) |
--jdbc-fetch-size <arg> | No | JDBC result set fetch size (default 2500) |
--help | No | Usage message, recognized only as the first argument |
Appending --help to a complete command runs the import instead of printing the usage message.