Skip to content
ToolShedby Wasim Shaikh

Velocity to FreeMarker Converter

Convert Liferay 6.x Velocity (.vm) templates to FreeMarker (.ftl) for 7.4 and DXP, including theme macros, $theme calls and loop counters. Lists what to check by hand.

Direction
Velocity (.vm)FreeMarker (.ftl)

Liferay variables in this template

These carry over unchanged. Most still exist in Liferay 7.4 and DXP templates, but check any your FreeMarker template relies on:

$theme

Conversion runs in your browser as you type. Liferay's Velocity macros (#language, #dockbar, #breadcrumbs…) and $theme calls become the Liferay 7 FreeMarker macros and taglibs. Anything the converter can't translate safely is listed under Check by hand.

Velocity to FreeMarker — Why and When

Liferay deprecated Velocity in 7.0 and later removed it, so Liferay 7.4 and the DXP quarterly releases only run FreeMarker (.ftl) themes, widget templates (ADTs), web content templates and email templates. Moving an old Liferay 6.x theme or a set of .vm templates means rewriting every directive, reference and Liferay macro. This converter does that mechanical pass, including portal_normal.vm and navigation.vm theme templates, and lists the few things it can't decide for you.

Velocity ↔ FreeMarker Syntax Reference

The core pattern mappings the converter applies. Keep this handy as a cheat sheet even when you are editing templates by hand.

PatternVelocityFreeMarker
Variable
$user.name
${user.name}
Quiet / silent referencePrints nothing when the value (or anything before it) is missing.
$!user.name
${(user.name)!}
Assign a variable
#set($x = 10)
<#assign x = 10>
IfFreeMarker needs a real boolean here. For an object or string use x?? (not null) or x?has_content.
#if($active)
<#if active>
Else if / else
#elseif($pending)
#else
<#elseif pending>
<#else>
Close ifVelocity reuses #end for every block; the converter tracks which block to close.
#end
</#if>
Greater thanA bare > ends a FreeMarker tag, so the converter writes gt / gte.
#if($total > 0)
<#if total gt 0>
Null check
#if($x != $null)
<#if x??>
Foreach loop
#foreach($item in $list)
<#list list as item>
Close foreach
#end
</#list>
Loop counter1-based. $foreach.index becomes item?index (0-based), $foreach.hasNext becomes item?has_next.
$velocityCount / $foreach.count
${item?counter}
List size
$list.size()
list?size
Range
#foreach($i in [1..5])
<#list 1..5 as i>
Define a macro
#macro(greet $name)
Hi $name
#end
<#macro greet name>
Hi ${name}
</#macro>
Call a macroConverted when the macro is defined in the same template.
#greet($user)
<@greet name=user />
Include a template
#parse("$full_templates_path/navigation.vm")
<#include "${full_templates_path}/navigation.ftl" />
Theme init
#parse($init)
<#include init />
Language key
#language("skip-to-content")
<@liferay.language key="skip-to-content" />
Language key with arguments
#language_format("go-to-x", [$site_name])
<@liferay.language_format arguments=site_name key="go-to-x" />
Dockbar / control menuThe Dockbar was replaced by the Control Menu in Liferay 7.0.
#dockbar()
<@liferay.control_menu />
Breadcrumbs
#breadcrumbs()
<@liferay.breadcrumbs />
Theme include
$theme.include($top_head_include)
<@liferay_util["include"] page=top_head_include />
Wrap portlet
$theme.wrapPortlet("portlet.vm", $content_include)
<@liferay_theme["wrap-portlet"] page="portlet.ftl">
  <@liferay_util["include"] page=content_include />
</@>
Embed a portlet
$theme.runtime("com_liferay_..._Portlet")
<@liferay_portlet["runtime"] portletName="com_liferay_..._Portlet" />
Line comment
## a comment
<#-- a comment -->
Block comment
#* a comment *#
<#-- a comment -->

How to Use This Converter

  1. Paste your Velocity. Drop your .vm template into the input panel, or click Load sample.
  2. Read the FreeMarker output. It regenerates live as you type — no button to press.
  3. Work through Check by hand. The converter lists anything it couldn't translate safely, such as #if($object) conditions or macros defined in another file.
  4. Test it in Liferay. Deploy the template and check the Liferay variables it uses still exist in 7.4 or DXP.
  5. Copy or download the result as a .ftl file.

Frequently Asked Questions

Why convert Velocity to FreeMarker?
Liferay deprecated Velocity templates in 7.0 and later removed Velocity support completely, so Liferay 7.4 and every DXP quarterly release (2025.Q1, 2026.Q1 and newer) only run FreeMarker. Themes, Application Display Templates (widget templates), web content templates and email templates from Liferay 6.x all have to be rewritten as .ftl files before they will work.
What does the converter handle automatically?
Directives (#set, #if/#elseif/#else, #foreach, #macro, #parse, #include, #break, #stop), the shared #end (closed as </#if>, </#list> or </#macro>), references ($x, $!x, ${x}, method calls and their arguments), comparison and word operators (> becomes gt, and/or/not become &&/||/!), $null checks (x??), loop counters ($velocityCount and $foreach.count/index/hasNext), ranges, string interpolation, comments, and calls to macros defined in the same template.
Does it convert Liferay theme macros and $theme calls?
Yes. #language and #language_format become <@liferay.language> and <@liferay.language_format>, #dockbar becomes <@liferay.control_menu />, #breadcrumbs, #css, #js and #date become their <@liferay.*> versions, #parse ($init) becomes <#include init />, $theme.include(...) becomes <@liferay_util["include"]>, $theme.runtime(...) becomes <@liferay_portlet["runtime"]> and $theme.wrapPortlet(...) becomes <@liferay_theme["wrap-portlet"]>. .vm file names in #parse become .ftl.
What do I still need to fix by hand?
The converter lists these under "Check by hand". The usual ones are #if($object) conditions (FreeMarker needs a real boolean, so use x?? or x?has_content), macros defined in another file, #set on a property, and #evaluate or #define. Also test the template in Liferay: some 6.2 variables and services were renamed or removed in 7.x.
Does it work in both directions?
Yes. Use the swap button to convert FreeMarker back to Velocity, including the Liferay macros and taglibs above. Velocity to FreeMarker is the main path; the reverse is best effort.
Is my template uploaded anywhere?
No. All conversion runs in your browser. Your template code never leaves your device.

Related Liferay Tools

Official Resources