Summary of New Features - freemarker/freemarker3 GitHub Wiki

Newer Terse Syntax

As long as a directive begins on its own line (not including whitespace) there is no need for pointy (or square) brackets. Thus,

[#if condition]
   ....
[/#if]

can now be written as:

#if condition
   ....
#endif

Note that the existing syntax still works, and in fact, is necessary if the directive is not at the beginning of a line. See here for more nitty-gritty details.

Terse unescaped interpolations

An interpolation can be written with an extra \ character to indicate that no escaping applies to it. Thus,

$\{">"}

will always output >, while ${">"}, if html escaping is in effect, would actually output >.

Newer #set/#var for declaration/setting of variables

In FreeMarker 3, the directives #assign/#local/#global are superseded by #set/#var. In fact, the deprecated style of defining variables only works if you have set legacy_syntax to true for your template. See here for the nitty gritty details.

More Liberal Assignment Instruction

In FreeMarker 3, you can write: #set foo.bar = baz as a shorthand for the Java code foo.put("bar", baz);

You can also write: #set foo[i] = bar which maps onto: foo.set(i,bar);

See here for more information.

The new :: operator for calling Java methods

In FreeMarker 3, you call Java methods using :: instead of .. This addresses a chronic confusion in the template language, which is that foo.bar potentially maps onto foo.get("bar") or refers to the method bar() that exists (possibly) in the underlying Java class. FreeMarker 3 gets rid of this basic ambiguity by requiring you to use the new :: operator when this is a Java method call. For more details see here.

Ternary Operator

This version of FreeMarker introduces a ternary operator that is the same as in Java, except that it is ?: rather than simply ?. (That is because the lone ? operator is being used for the built-in syntax.) Thus, rather than write:

[#if condition]
   [#assign foo = bar]
[#else]
   [#assign foo = baz]
[/#if]

you can simply write:

#set foo = condition ?: bar : baz

And it can also be convenient to use this in an interpolation:

${condition ?: bar : baz}

Assertions

An assertion in FTL works basically the same as assertions in Java:

#assert someCondition 

or

#assert someCondition : "optional message"

The #exec directive

The exec directive is used to call Java methods or FTL functions that do not return a value (or if they do return a value, you aren't interested in it!) Previously, in these cases, you could write:

${foo.someMethod()!} 

when you want to call a method with a void return type. Actually, since the whitespace stripping logic would not strip the leading/trailing whitespace in the above, you would typically want to write:

${foo.someMethod()!}[#t]

so that the extra spaces and newline are ignored. Or in some cases, you could write:

[#assign unused = foo.someMethod()]

when the method returns a value but you simply want to ignore it so you assign it to a variable that is never used.

Now, in both cases, you can simply write:

#exec foo::someMethod()

and regardless of whether the method actually returns something or not, the return value (or lack thereof) is simply ignored. Also, this way, it is clearer that we are calling the method purely for its side effects and not using the return value to set any variable or output anything.

Changes Under the Hood

A massive refactoring of the codebase was carried out and the real upshot is that all of the wrapping/unwrapping of variables as so-called "template models" is basically gone. (Though there are some vestiges of the older disposition, I grant.) But, by and large, when you have a number or a string in a template in FreeMarker 3, the underlying object is an actual java.lang.Number or java.lang.String. A list is a java.util.List, (most typically java.util.ArrayList) and a map is a java.util.Map (typically a java.util.HashMap). This should make it much simpler overall to work with FreeMarker and have a simpler conceptual model of what is going on.