2.3.33 - Apache FreeMarker Manual

Unsupported web browser - Use a modern browser to view this website!

- Home
- Manual
- Java API

- Contribute
- Report a Bug
- Download

Manual
Previous Next

# 2.3.33

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 like `obj.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 localized `Collator`-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 ...>`, and `concatedSeq?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 it `concatedSeq` 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 like `maybeLong?truncate_c(10, '')`, then certainly what you really want is `maybeLong[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.) Use `Environment.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 own `ObjectWrapper`, then set its `incompatible_improvements`, or just its `recordZeroArgumentNonVoidMethodPolicy` property to `BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD`). If in a Java record you have something like `int price()`, earlier you could only read the value in templates as `obj.price()`. With this improvement `obj.price` will do the same (and similarly, `obj["price"]()`, and `obj["price"]` will do the same). This has always worked for JavaBeans properties, like `int getPrice()` could always be used in templates as `obj.price`, in additionally to as `obj.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 be `getPrice`, and the property name would be `price` (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 therefore `DefaultObjectWrapper`: `recordZeroArgumentNonVoidMethodPolicy`, and `defaultZeroArgumentNonVoidMethodPolicy`. Each has enum type `freemarker.ext.beans.ZeroArgumentNonVoidMethodPolicy`, that can be `METHOD_ONLY`, `PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD`, or `BOTH_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` to `PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD`. The default with `incompatible_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 adds `getBeforeMethodCall(String key)`. If you have something like `obj.price()` in a template, where `obj` (after wrapping) implements that interface, then `getBeforeMethodCall("price")` called instead of `TemplateHashModel.get("price")`. This is needed for `ZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD` to work.

  - Added `GenericObjectModel`, which extends `StringModel` with implementing `MethodCallAwareTemplateHashModel`, and has a more telling name. `BeansWrapper`, and therefore `DefaultObjectWrapper` now creates `GenericObjectModel`-s instead of `StringModel`-s. This is like so regardless of any setting, like regardless of `incompatible_improvements`.

  - You shouldn't override `BeanModel.get(String)` anymore, but `BeanModel.get(String, boolean)`. If you have overridden `get`, 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 as `TemplateMarkupOutputModel`-s (which won't be auto-escaped even with this policy), not as `String`-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 each `FMParser` instance.

- FREEMARKER-218: Servlet, and JSP support classes now has a Jakarta variant in the new `freemarker.ext.jakarta.jsp` and `freemarker.ext.jakarta.servlet` packages. These are the slightly modified copies of `freemarker.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 tradition `javax` Serlvet/JSP/EL API-s. There's also `freemarker.ext.jakarta.servlet.WebappTemplateLoader`, which is the Jakarta equivalent of `freemarker.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 the `TemplateExceptionHandler.DEBUG_HANDLER` or `HTML_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 use `RETHROW_HANDLER`, and this change has no effect there.)

- When concatenating sequences (like Java `List`-s) with the `+` operation in templates, the resulting `TemplateSequenceModel` now also implements `TemplateCollectionModelEx` (with is similar to Java's `Iterable`). 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. (The `rmic` tool used to generate the stub classes was removed from the JDK, starting with JDK 15.)

- `Configuration.getVersion().getBuildDate()` will now always return `null`, 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 no `build.properties` to set up either, but you need to ensure that you have both JDK 8, and JDK 16, and that Gradle finds them. See the `README.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)

Previous Next