A step-by-step technical reference for upgrading from Liferay DXP 7.x to 2026 Q1
⚠️ Coming from Liferay 6.x?
Upgrading from Liferay 6.x is a multi-hop process and requires a different approach.
Please refer to our dedicated guide: "Upgrading to Liferay 7.x from 6.x" before continuing with this document.
Direct 6.x → 26 Q1 upgrade is not supported. You must first reach 7.4 GA, then proceed with this guide.
Analysis & Pre-Upgrade Planning
Before touching any code or running the upgrade tool, you need a thorough picture of your existing environment. This analysis phase defines the scope of work, flags blockers early, and prevents surprises mid-upgrade.
Planning your Liferay DXP Upgrade to 2026 Q1? Visit our Liferay Upgrade & Migration page to see how we can support your technical roadmap.
1.1 Environment Baseline
Document every component of your current installation against the table below. This becomes your upgrade checklist and your rollback reference.
| Component | Your Current Value | Required for 26 Q1 |
| Source version | Liferay DXP 7.x (note exact SP/FP) | Liferay DXP 2026 Q1 (26.0.x) |
| Application server | e.g. Tomcat 9.x | Tomcat 10.1 (minimum) |
| Java version | e.g. JDK 11 or JDK 8 | OpenJDK 21 LTS (Java 11 dropped) |
| Database | e.g. MySQL 5.7 / 8.0 | MySQL 8.0.x (5.7 unsupported) |
| Elasticsearch | e.g. 7.x | Elasticsearch 8.x |
| OS / Docker base image | e.g. Ubuntu 20.04 | Update to support Java 21 |
1.2 Custom Assets Inventory
Before upgrading, catalogue every custom asset in your instance. Each category has a different migration path and risk level.
| | Asset Type | What to Check | Migration Path |
| ☐ | Custom OSGi Modules | Count bundles; check javax.* imports, bnd.bnd, BOM alignment | Rebuild against 26 Q1 BOM + jakarta migration |
| ☐ | Custom Non-OSGi Portlets | WAR portlets, legacy plugins; check servlet API usage | Migrate to OSGi portlet or Client Extension |
| ☐ | Client Extensions | List all LXC / remote apps; check compatibility with new DXP APIs | Review API compatibility; re-deploy |
| ☐ | Custom Themes | Check base theme, Clay version, FreeMarker templates | Rebuild as Theme Client Extension |
| ☐ | Custom Objects | List object definitions, fields, relationships, actions | Re-export/import; verify picklists and validations |
| ☐ | Fragments | Count fragment collections; check for deprecated JS APIs | Validate in new renderer; update JS if needed |
| ☐ | Web Contents | Check structures, templates, and any Velocity templates | Migrate Velocity → FreeMarker (Velocity removed) |
| ☐ | Blogs | Images, categories, display pages | Auto-migrated; verify display page templates |
| ☐ | Document and Media | Check custom metadata, DL file entry types, preview renderers | Verify file entry type migration; check adaptive media |
Tip: Export the table above into a spreadsheet and assign an owner and status column to each row before starting the upgrade.
1.3 Modernisation Mapping: What to Migrate to What
Liferay 26 Q1 shifts several legacy extension patterns to modern equivalents. Use this table to map your current customisations to their target architecture before writing any code.
| Legacy Pattern | → | Modern Replacement | Notes |
| Frontend JS Portlet (WAR) | → | Remote App / IFrame CX | No server-side Java; pure frontend deployed independently |
| OSGi MVC Portlet (simple UI) | → | Custom Element Client Extension | React/Vue/Angular; communicates via REST/GraphQL |
| Custom Theme (.war) | → | Theme Client Extension | CSS + JS only; extends base theme tokens |
| Ext Plugin | → | Configuration + Fragment CX | Ext plugins fully removed in 7.4+; replace with config |
| Service Builder Entity | → | Liferay Object Definition | For simple domain models; use Service Builder for complex |
| Velocity Templates | → | FreeMarker Templates | Velocity engine removed; mandatory migration |
| Legacy Hook (portal.properties) | → | OSGi Configuration Admin (.cfg) | Deploy .config / .cfg files to osgi/configs/ |
| WAR Portlet | → | OSGi Bundle Portlet | Convert to module using Blade CLI scaffolding |
ℹ️ Service Builder vs Objects — When to choose which?
Use Liferay Objects if: your entity is simple, no complex business logic, no direct DB joins needed.
Keep Service Builder if: you need custom SQL finders, complex relationships, workflow integration, or performance-critical queries.
You can mix both in the same project — Objects for config-driven data, Service Builder for transactional core.
Known Challenges & What to Expect
Understanding the major breaking changes before you start will save significant debugging time. These are the most common blockers encountered during a 7.x to 26 Q1 upgrade.
2.1 Jakarta EE Namespace Migration
The single largest code change. Every import of javax.* in your custom Java code must become jakarta.* — this is a hard compile-time failure if missed.
⛔ Compile-time break
Any custom module still importing javax.* packages will fail to compile and resolve in the OSGi runtime.
This includes Java source files AND XML deployment descriptors (web.xml, portlet.xml, persistence.xml).
| Old (javax.*) | → | New (jakarta.*) | Affects |
| javax.servlet.* | → | jakarta.servlet.* | Filters, Listeners, Servlets |
| javax.portlet.* | → | jakarta.portlet.* | All custom portlets |
| javax.persistence.* | → | jakarta.persistence.* | JPA entities, DynamicQuery |
| javax.inject.* | → | jakarta.inject.* | CDI @Inject points |
| javax.ws.rs.* | → | jakarta.ws.rs.* | JAX-RS REST endpoints |
| javax.validation.* | → | jakarta.validation.* | Bean validation annotations |
| javax.annotation.* | → | jakarta.annotation.* | @PostConstruct, @PreDestroy |
| javax.el.* | → | jakarta.el.* | EL in JSP / FreeMarker |
XML descriptor namespace URI also changes:
<!-- BEFORE -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0" />
<!-- AFTER -->
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="6.0" />
2.2 Java 21 & Tomcat 10.1 Requirement
- Java 11 is end-of-life for Liferay 26 Q1 — you must upgrade the JDK on all nodes
- Tomcat 9.x implements Servlet 4.0 (javax.servlet) — incompatible with Jakarta EE 10 Servlet 6.0
- Update Docker base images: FROM eclipse-temurin:21-jdk-jammy
- Review GC flags — G1GC is still the default; ZGC is available for low-latency workloads on Java 21
2.3 OSGi Bundle Resolution Failures
After deployment, bundles may show as INSTALLED rather than ACTIVE in the Gogo Shell. This is almost always caused by unresolved Import-Package constraints that still reference javax.* namespaces in bnd.bnd.
# Diagnose via Gogo Shell
g! lb -s | grep -v Active
g! diag <bundleId>
# Common failure output:
Unresolved requirement: Import-Package: javax.portlet;version="[3.0,4.0)"
# Fix: update bnd.bnd -> jakarta.portlet;version="[3.0,4.0)"
# Force refresh after fixing:
g! refresh <bundleId>
2.4 Service Builder & Hibernate 6
Liferay 26 Q1 bundles Hibernate 6 which removes the legacy Criteria API. Custom finders using DynamicQuery with RestrictionsFactoryUtil patterns may fail silently or throw runtime exceptions.
// PROBLEMATIC (Hibernate 6) — Criteria API removed
DynamicQuery dq = dynamicQueryFactory.forClass(MyEntity.class);
dq.add(RestrictionsFactoryUtil.like("field", "%value%"));
// RECOMMENDED — Use ActionableDynamicQuery
ActionableDynamicQuery adq = myEntityLocalService.getActionableDynamicQuery();
adq.setAddCriteriaMethod(dq -> {
dq.add(RestrictionsFactoryUtil.ilike("field", "%value%"));
});
adq.performActions();
2.5 Theme & Clay 4 Breaking Changes
- Classic theme and Unstyled theme are removed — do not extend them
- clay:dropdown-menu taglib removed → use clay:dropdown
- clay:navigation-bar taglib removed → use clay:vertical-nav
- clay:management-toolbar API changed — review props
- If you are migrating to a Theme Client Extension, the build pipeline is entirely different (no Liferay Theme Toolkit; pure CSS/JS bundle)
2.6 Velocity Templates Removed
If your Web Content Structures or Display Pages use Velocity (.vm) templates, they will not render in 26 Q1. All templates must be migrated to FreeMarker (.ftl) before upgrading.
ℹ️ How to find Velocity templates
Go to: Site Administration → Content & Data → Web Content → Templates
Filter by Language = Velocity.
Export and rewrite each one in FreeMarker before proceeding with the upgrade.
Upgrade Steps
Follow these steps in sequence. Each step should be validated before proceeding to the next. We strongly recommend completing the full process in a staging environment before touching production.
⚠️ Before you start
- Take a full database backup: mysqldump -u root -p liferay_db > backup_$(date +%Y%m%d).sql
- Back up $LIFERAY_HOME/osgi/, $LIFERAY_HOME/deploy/, and portal-ext.properties
- Document your current portal.properties customisations
- Ensure you have rollback procedures agreed with your team
Step 1 — Upgrade Java & Application Server
1. Install OpenJDK 21 on all application server nodes
2. Replace Tomcat 9.x with Tomcat 10.1
3. Update setenv.sh JVM flags for Java 21
# setenv.sh — update JVM settings for Java 21
CATALINA_OPTS="$CATALINA_OPTS -Xms2g -Xmx4g"
CATALINA_OPTS="$CATALINA_OPTS -Dfile.encoding=UTF-8"
CATALINA_OPTS="$CATALINA_OPTS -Djava.locale.providers=JRE,COMPAT,CLDR"
# Optional: enable ZGC for low-latency (Java 21+)
# CATALINA_OPTS="$CATALINA_OPTS -XX:+UseZGC"
Step 2 — Update the Liferay Workspace
4. Update liferay.workspace.product in gradle.properties
5. Remove all hardcoded com.liferay.* version overrides
6. Refresh dependencies
# gradle.properties
liferay.workspace.product=dxp-2026.q1.0
# Then refresh:
blade gw initBundle
blade gw dependencies --refresh-dependencies
Step 3 — Migrate Velocity Templates to FreeMarker
7. Export all Web Content templates using Velocity
8. Rewrite each as a FreeMarker (.ftl) template
9. Update the template language in the Web Content admin
FreeMarker equivalent of common Velocity syntax is documented at: learn.liferay.com/w/freemarker-migration
Step 4 — Run Jakarta Namespace Migration on Custom Modules
10. Install Eclipse Transformer CLI
11. Run transformer across modules directory
12. Manually review string literals, bnd.bnd, and XML descriptors
# Run Eclipse Transformer on your modules
java -jar org.eclipse.transformer.cli.jar \
./modules ./modules-jakarta \
-tf org.eclipse.transformer.jakarta.JakartaTransformRelocation
# Verify no javax imports remain
grep -r 'import javax\.' ./modules --include='*.java' | wc -l
# Expected output: 0
# Also update bnd.bnd Import-Package directives manually
# javax.portlet;version="[3.0,4.0)" -> jakarta.portlet;version="[3.0,4.0)"
# javax.servlet;version="[4.0,5.0)" -> jakarta.servlet;version="[6.0,7.0)"
Step 5 — Rebuild Service Builder Entities
13. Re-run buildService task against the updated workspace
14. Review generated *FinderImpl for Hibernate 6 incompatible patterns
15. Migrate any DynamicQuery Criteria usage to ActionableDynamicQuery
cd modules/your-service-module
blade gw buildService
blade gw compileJava
# If using Objects instead of Service Builder for simple entities:
# Export the object definition JSON from your 7.x instance and
# import it into the 26 Q1 instance via:
# Objects → Export / Import
Step 6 — Migrate Themes
Classic theme is removed. You have two options:
- Option A: Extend the _styled base theme (traditional theme WAR, still supported)
- Option B: Rebuild as a Theme Client Extension (recommended for new architecture)
Option A — Traditional Theme (extend _styled)
cd themes/your-theme
# Update liferay-look-and-feel.xml
# <compatibility-version>2026.q1.0</compatibility-version>
# <extends>_styled</extends> <- change from 'classic'
gulp upgrade # auto-detects deprecated Clay taglibs
# Manually replace removed taglibs in templates/:
# clay:dropdown-menu -> clay:dropdown
# clay:navigation-bar -> clay:vertical-nav
Option B — Theme Client Extension
cd modules/your-service-module
blade gw buildService
blade gw compileJava
# If using Objects instead of Service Builder for simple entities:
# Export the object definition JSON from your 7.x instance and
# import it into the 26 Q1 instance via:
# Objects → Export / Import
# Deploy:
blade gw deploy
# Then assign in: Site Settings → Custom Apps → Theme CSS
Step 7 — Migrate Frontend JS Portlets to Client Extensions
If you have React, Angular, or Vue-based frontend portlets built as OSGi modules or WAR files, the recommended path is to convert them to Custom Element Client Extensions.
# Scaffold a Custom Element Client Extension
blade create -t client-extension -c custom-element my-react-widget
# client-extension.yaml example:
my-react-widget:
name: My React Widget
type: customElement
htmlElementName: my-react-widget
url: index.js
cssURLs:
- index.css
instanceable: true
portletCategoryName: category.client-extensions
Step 8 — Run the Database Upgrade Tool
⚠️ Critical: Liferay must NOT be running during this step
Stop all Liferay nodes before running the DB upgrade tool.
The upgrade is irreversible — ensure your backup from Step 0 is verified first.
cd $LIFERAY_HOME/tools/portal-tools-db-upgrade-client
./db_upgrade.sh \
-j "-Dfile.encoding=UTF-8 -Xmx4096m" \
--log-level DEBUG
# Monitor progress:
tail -f $LIFERAY_HOME/logs/upgrade*.log
# After completion, check for errors or skipped steps:
grep -i 'ERROR\|WARN\|SKIP' $LIFERAY_HOME/logs/upgrade*.log
Step 9 — Deploy Custom Modules & Validate Bundle Resolution
16. Start Liferay with the updated Tomcat 10.1 + Java 21
17. Deploy all custom OSGi bundles to deploy/ or osgi/modules/
18. Open the Gogo Shell and verify all bundles reach ACTIVE state
# Open Gogo Shell via telnet (if enabled) or via Control Panel > Gogo Shell
g! lb -s | grep -v ' Active '
# If list is empty: all bundles resolved successfully
# If bundles show as INSTALLED or RESOLVED but not ACTIVE:
g! diag <bundleId>
# Fix the reported unresolved requirement, redeploy, then:
g! refresh <bundleId>
Step 10 — Verify Content, Objects & Fragments
16. Navigate to Web Content and verify all structures and templates render
17. Open Object Definitions admin — confirm all custom objects, fields, and picklists are present
18. Open Fragments admin — preview each fragment collection; fix any deprecated JS APIs
18. Open Blogs admin — verify display page template assignments
Open Documents and Media — check custom metadata sets and adaptive media renditions
Breaking Changes Reference
Use this section as a checklist. Tick off each item as you confirm it is resolved in your upgrade.
4.1 Platform-Level Changes
| Change | Impact | Action Required |
| Java 11 dropped | High | Upgrade to OpenJDK 21 LTS |
| Tomcat 9 incompatible | High | Install Tomcat 10.1 |
| javax.* → jakarta.* | High | Full namespace migration on all custom code |
| Hibernate 6 (Criteria removed) | High | Migrate DynamicQuery patterns |
| MySQL 5.7 unsupported | Medium | Upgrade to MySQL 8.0.x |
| Elasticsearch 7 → 8 | Medium | Upgrade Elasticsearch cluster |
| Velocity templates removed | Medium | Migrate all .vm to .ftl |
| Classic & Unstyled themes removed | Medium | Extend _styled or use Theme CX |
4.2 Jakarta Package Quick Reference
| Old (javax.*) | → | New (jakarta.*) | Affects |
| javax.servlet.* | → | jakarta.servlet.* | Filters, Listeners, Servlets |
| javax.portlet.* | → | jakarta.portlet.* | All custom portlets |
| javax.persistence.* | → | jakarta.persistence.* | JPA entities, JPQL |
| javax.inject.* | → | jakarta.inject.* | CDI @Inject |
| javax.ws.rs.* | → | jakarta.ws.rs.* | JAX-RS REST |
| javax.validation.* | → | jakarta.validation.* | Bean validation |
| javax.annotation.* | → | jakarta.annotation.* | @PostConstruct, @PreDestroy |
| javax.el.* | → | jakarta.el.* | EL in JSP/FTL |
4.3 Removed APIs
- Hibernate Criteria API — use ActionableDynamicQuery or native JPQL
- clay:dropdown-menu taglib — use clay:dropdown
- clay:navigation-bar taglib — use clay:vertical-nav
- com.liferay.portal.kernel.servlet.taglib.ui.* (several UI tag classes removed)
- Legacy Ehcache XML config — migrate to portal.cache.multi.jvm OSGi config
- WAR hook plugins — replace with OSGi fragments or configuration admin
Fixes & Code Patterns
Copy-paste-ready fixes for the most common upgrade issues.
Fix 1 — Bulk javax → jakarta Rename
# Automated rename across all Java source files
find ./modules -name '*.java' | xargs sed -i \
-e 's/import javax\.servlet/import jakarta.servlet/g' \
-e 's/import javax\.portlet/import jakarta.portlet/g' \
-e 's/import javax\.persistence/import jakarta.persistence/g' \
-e 's/import javax\.inject/import jakarta.inject/g' \
-e 's/import javax\.ws\.rs/import jakarta.ws.rs/g' \
-e 's/import javax\.validation/import jakarta.validation/g' \
-e 's/import javax\.annotation/import jakarta.annotation/g'
# Verify nothing remains:
grep -r 'import javax\.' ./modules --include='*.java'
Fix 2 — bnd.bnd Import-Package Update
# BEFORE
Import-Package: javax.portlet;version="[3.0,4.0)",\
javax.servlet;version="[4.0,5.0)"
# AFTER (note: servlet version jumps to 6.0 under Jakarta EE 10)
Import-Package: jakarta.portlet;version="[3.0,4.0)",\
jakarta.servlet;version="[6.0,7.0)"
Fix 3 — DynamicQuery Migration
// BEFORE — Hibernate Criteria (fails on Hibernate 6)
DynamicQuery dq = dynamicQueryFactory.forClass(Article.class);
dq.add(RestrictionsFactoryUtil.like("title", "%" + keyword + "%"));
return articlePersistence.findWithDynamicQuery(dq);
// AFTER — ActionableDynamicQuery
List<Article> results = new ArrayList<>();
ActionableDynamicQuery adq = articleLocalService.getActionableDynamicQuery();
adq.setAddCriteriaMethod(dq -> {
dq.add(RestrictionsFactoryUtil.ilike("title", "%" + keyword + "%"));
});
adq.setPerformActionMethod((Article a) -> results.add(a));
adq.performActions();
return results;
Fix 4 — XML Descriptor Namespace
<!-- web.xml BEFORE -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- web.xml AFTER -->
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
Fix 5 — Cache Configuration (OSGi Config)
# Create file in: $LIFERAY_HOME/osgi/configs/
# Filename: com.liferay.portal.cache.multiple.internal.module.configuration
# .MultiplePortalCacheManagerConfiguration.config
clusterLinkEnabled=B"true"
replicatedPortalCacheEnabled=B"true"
# Remove any legacy ehcache-*.xml files from WEB-INF/
# They are silently ignored in 26 Q1
QA & Validation
After completing all upgrade steps, run through this validation checklist before sign-off.
6.1 OSGi Health Check
# All custom bundles must be ACTIVE
g! lb -s | grep -v Active
# Expected: empty output
# Check for any ERROR-level log entries at startup
grep -i 'ERROR' $LIFERAY_HOME/logs/liferay*.log | grep -v 'expected'
6.2 Functional Regression Areas
| Area to Test | What to Validate |
| Custom portlets | Render correctly, no 500 errors, all actions work |
| Client extensions | Remote apps load, custom elements initialise |
| Themes | All pages render with correct styling and Clay components |
| Web Content | Structures render, FreeMarker templates output correct HTML |
| Custom Objects | All object entries visible, forms submit, relationships intact |
| Fragments | Fragment collections visible, JS interactions work |
| Blogs | Display pages assigned, images render, categories preserved |
| Documents and Media | Files accessible, metadata sets present, previews render |
| Search | Site search returns results, facets work |
| Workflow | Existing workflow definitions load, approvals route correctly |
| REST APIs | All custom Headless API endpoints return 200 |
| User / Roles / Permissions | Existing roles intact, permission inheritance working |
6.3 Performance Sanity Checks
- Homepage load time within acceptable range (compare with pre-upgrade baseline)
- REST API p95 response time not regressed
- Heap memory stabilises under load (no continuous growth indicating leak)
- Startup time measured and documented for baseline comparison on next upgrade
Support & Reference Resources
7.1 Official Liferay Documentation
- Release Notes 2026 Q1: learn.liferay.com/w/liferay-release-2026q1
- Breaking Changes Reference: learn.liferay.com/w/breaking-changes-2026q1
- Database Upgrade Tool: learn.liferay.com/w/database-upgrade-tool
- Client Extension Overview: learn.liferay.com/w/client-extensions
- Objects Documentation: learn.liferay.com/w/liferay-objects
- Fragments Documentation: learn.liferay.com/w/page-fragments-and-layouts
7.2 Jakarta EE Resources
- Jakarta EE 10 Specification: jakarta.ee/specifications
- Eclipse Transformer CLI: github.com/eclipse/transformer
- Jakarta Migration Guide: jakarta.ee/resources/migration
7.3 Tooling
- Blade CLI — workspace management, scaffolding, deploy: blade.liferay.com
- Liferay Upgrade Planner (IntelliJ Plugin) — step-by-step guided upgrade
- Gogo Shell (lb, diag, refresh) — OSGi runtime diagnostics
- Eclipse Transformer — automated javax → jakarta migration
- gulp upgrade — detect deprecated Clay taglibs in theme projects
7.4 Key Lessons & Tips
✅ Top tips for a successful upgrade
- Inventory first, code second — complete Section 1 fully before writing any migration code.
- Commit the Eclipse Transformer run as a single atomic commit — makes it easy to diff and review the bulk rename.
- Always update bnd.bnd Import-Package after running the transformer — it does not always catch these.
- Run the DB upgrade on a full production data clone, not just a schema dump — data volume exposes timeout issues.
- Migrate Velocity templates early — they are often forgotten and block content rendering post-upgrade.
- Budget 2x your estimated time. Jakarta migration + Service Builder regeneration + theme rebuild always surfaces surprises.
- Keep the Gogo Shell open on a second screen during first startup — bundle errors surface there first.