Release date: 2024-06-01
Please note that with this version the minimum required Java version was increased from Java 7 to Java 8. Also for the few who rely on Servlet and/or JSP support, the minimum is now increased to Servlet 3.0, and JSP 2.2 (which are still very old versions from 2011).
Changes on the FTL side
-
FREEMARKER-183: If FreeMarker is configured like so, values in Java records can now be referred like
obj.price
, instead of likeobj.price()
. Furthermore, FreeMarker can now be configured to allow this for all 0-argument non-void
methods. See more details in the Changes on the Java side section below. -
GitHub PR 87 Comparing strings is now way faster, if the
incompatible_improvements
setting is at least 2.3.33. If your template does lot of string comparisons, this can mean very significant speedup. With this enabled, we use a simpler way of comparing strings, and because templates were only ever allowed equality comparisons between strings (not less-than, or greater-than), it's very unlikely to change the behavior of your templates. (Technically, what changes is that instead of using Java's localizedCollator
-s, we switch to a simple binary comparison after UNICODE NFKC normalization. So, in theory it's possible that for some locales two different but similarly looking characters were treated as equal by the collator, but will count as different now. But it's very unlikely that anyone wanted to depend on such fragile logic anyway. Note again that we still do UNICODE normalization, so combining characters won't break your comparison.) -
When concatenating many sequences (like of Java
List
-s) with the+
operator, the resulting sequence is now much less slow to read. (Background: the+
operator, when applied on two sequences, produces a result sequence that just view, not a copy of the original sequences. Thus is very fast to add together long sequences. But, if you concatenate many, like hundreds, of sequences, reading the sequence back will become expensive. It was always like that, so it's still not recommended to concatenate more then a few tens of sequences. But if that recommendation is not kept, now the slowdown can be way less punishing.)-
Iteration (like listing) is now reasonably fast, and need not be concerned about. The speed of fetching the next item is now mostly independent of the number of sequences concatenated, while earlier it has become slower as that number grew. By iteration we mean going through all the items, strictly in order, without using an index explicitly. Examples of iteration are
<#list concatedSeq as ...>
, andconcatedSeq?join(', ')
. -
Accessing items by index, like
concatedSeq[i]
, is now much faster than before, but still can be slow if you had concatenated a lot of sequences. Item access speed is now roughly O(N), where N is number of concatenated sequences (not the number of items!), so traversing the whole sequence by index is O(N²). -
In previous versions
concatedSeq?size
could cause stack overflow if itconcatedSeq
was concatenated together from thousands of sequences. Now the stack usage is constant and negligible.
-
-
FREEMARKER-216: Fixed
IllegalAccessException
-s on Java 16 and later, when calling a public method that was last overridden by a public class that's however JDK internal (see JEP 396). More generally, we avoid this error if a class comes from the package of a Java module that doesn't export that package to the module where FreeMarker resides, by looking for an accessible public method in a public class that the unaccessible one overrides (maybe indirectly). If there's no such method, then the method won't be visible (despite that both the method and the class is public). -
FREEMARKER-219: The "truncate" family of built-ins, as in
maybeLong?truncate(10, '')
, if the terminator string is set to 0 length, now it will not add a space before the terminator string when the cut happened exactly after the end of a word. (Note that if you are using something likemaybeLong?truncate_c(10, '')
, then certainly what you really want ismaybeLong[0 ..* 10]
, as that doesn't do trimming at the cut.) -
GitHub PR 89: Added
TemplateProcessingTracer
mechanism, that can be used to monitor coverage, and performance inside templates as they are being processed. For example, you could construct a heat map for how often the different parts run, or finding the performance hot spots. (There can be other creative uses, like watching for a variable to have a certain value.) UseEnvironment.setTemplateProcessingTracer(TemplateProcessingTracer)
to enable this kind of monitoring. (See the API docs for more.)
Changes on the Java side
-
FREEMARKER-183: Better support for Java records, if you set the
incompatible_improvements
setting to 2.3.33 or higher (or if you create your ownObjectWrapper
, then set itsincompatible_improvements
, or just itsrecordZeroArgumentNonVoidMethodPolicy
property toBOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD
). If in a Java record you have something likeint price()
, earlier you could only read the value in templates asobj.price()
. With this improvementobj.price
will do the same (and similarly,obj["price"]()
, andobj["price"]
will do the same). This has always worked for JavaBeans properties, likeint getPrice()
could always be used in templates asobj.price
, in additionally to asobj.getPrice()
. Now this also works for Java records, as there we simply treat all methods that has 0 arguments, and non-void
return type as if it was a JavaBean property read method. Except, here the name of the method is exactly the same as the name of the faked JavaBeans property (price
), while with real JavaBeans the read method name typically would begetPrice
, and the property name would beprice
(so we have two separate names). There are some strange technical tricks involved for the same name to be usable in both ways, but as far as most users care, it just works.Some more technical changes:
-
Added two new settings to
BeansWrapper
, and thereforeDefaultObjectWrapper
:recordZeroArgumentNonVoidMethodPolicy
, anddefaultZeroArgumentNonVoidMethodPolicy
. Each has enum typefreemarker.ext.beans.ZeroArgumentNonVoidMethodPolicy
, that can beMETHOD_ONLY
,PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD
, orBOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD
. Therefore:-
Note that with
defaultZeroArgumentNonVoidMethodPolicy
you can set similar behavior to non-records. That is, you can call 0 argument non-void methods without()
, if you want. It's only meant to be used for methods that are mere value readers, and has no side effect. -
For records, you can enforce proper style with setting
recordZeroArgumentNonVoidMethodPolicy
toPROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD
. The default withincompatible_improvements
2.3.33 is more lenient, as there using()
is allowed (for backward compatibility, and because people often just use the Java syntax).
-
-
Added new interface,
freemarker.template.MethodCallAwareTemplateHashModel
, which addsgetBeforeMethodCall(String key)
. If you have something likeobj.price()
in a template, whereobj
(after wrapping) implements that interface, thengetBeforeMethodCall("price")
called instead ofTemplateHashModel.get("price")
. This is needed forZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD
to work. -
Added
GenericObjectModel
, which extendsStringModel
with implementingMethodCallAwareTemplateHashModel
, and has a more telling name.BeansWrapper
, and thereforeDefaultObjectWrapper
now createsGenericObjectModel
-s instead ofStringModel
-s. This is like so regardless of any setting, like regardless ofincompatible_improvements
. -
You shouldn't override
BeanModel.get(String)
anymore, butBeanModel.get(String, boolean)
. If you have overriddenget
, then see in the Javadoc for more.
-
-
GitHub PR 88: Added a new possible value for the
auto_escaping_policy
configuration setting,force
(Configuration.FORCE_AUTO_ESCAPING_POLICY
). This policy is to always require auto-escaping, to avoid accidents where because of misconfiguration, or a mistake of the template author it's disabled. With this policy, using output formats that don't support escaping will not be allowed. Using built-ins, and directives that disable auto-escaping (like?no_esc
) will also be errors (on parse-time). Note that if markup (like HTML) comers from the data model, then with this policy you will have to ensure that they come asTemplateMarkupOutputModel
-s (which won't be auto-escaped even with this policy), not asString
-s, because the template authors can't disable escaping for the value anymore. -
FREEMARKER-214, GitHub PR 90: Update JavaCC (used for generating the template parser) from 6.1.2 to 7.0.12, to avoid creating new
LookAheadSuccess
with stack trace instance for eachFMParser
instance. -
FREEMARKER-218: Servlet, and JSP support classes now has a Jakarta variant in the new
freemarker.ext.jakarta.jsp
andfreemarker.ext.jakarta.servlet
packages. These are the slightly modified copies offreemarker.ext.servlet
,freemarker.ext.jsp
(which are also present in the same FreeMarker jar), that depend on the Jakarata API-s (jakarta.servlet
,jakarta.servlet.jsp
,jakarta.el
, etc.), instead of the traditionjavax
Serlvet/JSP/EL API-s. There's alsofreemarker.ext.jakarta.servlet.WebappTemplateLoader
, which is the Jakarta equivalent offreemarker.cache.WebappTemplateLoader
. (Note that Jakarta packages/classes are not visible in the FreeMarker source code, as they are generated during build from the pre-Jakarta classes.) -
When
FreemarkerServlet
was used with theTemplateExceptionHandler.DEBUG_HANDLER
orHTML_DEBUG_HANDLER
, on Jetty the response web page was possibly empty or partial, as the Jetty has abruptly closed the connection after we flushed the HTTP response with status code 200, and yet thrown an exception. Now in this situation we only log the exception, but don't throw it. (Production system should always useRETHROW_HANDLER
, and this change has no effect there.) -
When concatenating sequences (like Java
List
-s) with the+
operation in templates, the resultingTemplateSequenceModel
now also implementsTemplateCollectionModelEx
(with is similar to Java'sIterable
). It's because using that is much more efficient then indexed access, if the sequence was concatenated together from a lot of sequences. -
We don't generate RMIC stub classes for
freemarker.debug.impl.Rmi*Impl
classes anymore. Almost nobody uses that API, but if you do, you certainly already rely on dynamic stubs anyway. (Thermic
tool used to generate the stub classes was removed from the JDK, starting with JDK 15.) -
Configuration.getVersion().getBuildDate()
will now always returnnull
, as we don't store the build date anymore, to make the build reproducible. For same reason,META-INF/MANIFEST.FM
now will not store any timestamps, nor information about the build environment. -
Build changes (in case you build FreeMarker itself, not just depend on it):
-
FREEMARKER-204, GitHub PR 79: Switched from Ant, to Gradle. IDE setup now involves no lengthy manual adjustments, assuming it's fairly recent IDE with decent Gradle support. There's on
build.properties
to set up either, but you need to ensure that you have both JDK 8, and JDK 16m and that Gradle finds them. See theREADME.md
for more instructions. (Gradle itself is self-installing, of course.) -
JavaDoc is now generated with JDK 16 (so now we have search on it)
-
The build is now reproducible (see reproducible-builds.org). However, therefore it doesn't contain any build timestamps anymore.
-
-
Minimum requirements were increased:
-
Java 8 (or higher)
-
If Servlet-related features are used, Servlet 3.0 (or higher)
-
If JSP-related features are used, JSP 2.2 (or higher)
-