2.3.22

Date of release: 2015-03-01

Note that since 2.3.22 is designed to be fully backward compatible with the previous 2.3.x releases, some of the improvements and fixes described below are only activated when you specifically ask for 2.3.22 "incompatible improvements" (it's always clearly indicated), because they could, with very small chance, break existing applications. For actively maintained applications it's probably better to allow them. See how to set "incomplatible improvements" here.

Changes on the FTL side

  • New built-ins: api and has_api. value?api provides access to the API (usually, the Java API) of value, like value?api.someJavaMethod() or value?api.someBeanProperty), if the value itself supports exposing its API. This meant to be used rarely, when you need to call a Java method of an object, but the by-design simplistic view of the value that FreeMarker exposes to the templates hides that, and there's no equivalent built-in either. For example, when you put a Map into the data-model (and you are using the default object wrapper), myMap.myMethod() in a template basically translates to ((Method) myMap.get("myMethod")).invoke(...) in Java, thus you can't call myMethod. If, however, you write myMap?api.myMethod() instead, that means myMap.myMethod() in Java. Similarly, myMap?api.myProperty translates to myMap.getMyProperty() in Java, instead of to myMap.get("myProperty").

    If you can, rely on the capabilities of the FTL types and the related built-ins as far as possible. Using ?api is only the last resort.

    Using ?api also happens to offer a workaround for the lack of non-String Map key support in FTL's [] operator (as in myMap[key]), because now you can write myMap?api.get(nonStringKey).

    ?api is not enabled by default and isn't available for all values. See more here...

  • Identifiers (like someVariable) can now contain minus (-), dot (.), and colon (:) at any position, but those characters must be escaped with a preceding backslash (\), or else they would be interpreted as operators. For example, to read the variable whose name is "data-id", the correct expression is data\-id, as data-id would be interpreted as "data minus id". This also works for named macro parameters, which is useful when you want to accept arbitrary HTML attributes in a catch-all parameter, like in <@box class="someCssClass" data\-id=product.id />. (When you enumerate the catch-all parameter names inside the macro, the key string you get is "data-id" without \ of course.)

  • Added ?lower_abc and ?upper_abc. This converts 1, 2, 3, etc., to the string "a", "b", "c", etc. (or for "A", "B", "C", etc.). When reaching "z", it continues like "aa", "ab", etc. This is the same logic that you can see in column labels in spreadsheet applications (like Excel or Calc). More details...

  • Added ?keep_before_last and ?keep_after_last. Example: "foo.bar.txt"?keep_before_last(".") returns "foo.bar", "foo.bar.txt"?keep_after_last(".") returns "txt". (These work like ?keep_before and ?keep_after, but those look for the first occurrence of the separator.)

  • Added many missing UNICODE letters and digits to the set of legal identifier characters, like Korean letters (bug fixed: [129 ])

  • Error message quality improvements:

    • Several improvements when calling custom JSP tags; see them in its own section later.

    • Bug fixed: When localized lookup or template acquisition has kicked in, error messages have still quoted the name used for requesting the template, rather that the actual template source name (like foo.ftl instead of foo_en.ftl, when the template was get as foo.ftl, but behind the scenes was loaded from foo_en.ftl).

    • "Template not found" errors are now more detailed, giving hints about accidentally using \ instead of /, or backing out of the TemplateLoader's root directory.

    • The #setting directive gives more helpful error message when the setting name is not recognized, and lists the allowed setting names or a correction suggestion.

    • When a bad special variable name (.name) is encountered, the list of available names is shown in the error message.

    • When Map.get or Map.containsKey of a wrapped Map throws a ClassCastException or NullPointerException, the error will point to the causing FTL expression (with some explanation), rather than bubbling up as low level runtime error.

Changes on the Java side

  • Object wrapping improvements:

    • DefaultObjectWrapper, only with its incompatible_improvements set to 2.3.22 (see how here...), or more precisely, with its new useAdaptersForContainers setting set to true (which defaults to true when incompatible_improvements is set to 2.3.22): It doesn't copy Map-s, List-s, and arrays anymore when wrapping them into TemplateModel-s (which is the interface through with templates access all values), just wraps them into thin TemplateModel adapters, that will reach the original object for all operations. The wrapped values will be instances of the new DefaultMapAdapter, DefaultListAdapter and DefaultArrayAdapter classes, instead of the legacy (copying) SimpleHash and SimpleSequence classes. (Note that many projects use pure BeansWrapper instead of DefaultObjectWrapper, which has always used the adapter approach, albeit a different implementation of it. As the shortcomings of DefaultObjectWrapper are fixed now, it's always recommended over BeansWrapper, as BeansWrapper gives quite confusing multi-typed values and is substantially slower.)

      While keeping backward compatibility as much as possible was an important factor in this change, this is a quite deep change, so you may want to review the consequences and reasons here... (But again, this change is not active by default, so merely updating FreeMarker wont risk the stability of existing applications)

    • Added TemplateMethodModelEx BeansWrapper.wrap(Object object, Method method) for wrapping methods without wrapping their parent object and without going through overloaded method selection on invocation time.

    • Bug fixed [372 ]: ClassCastException when a SortedMap (typically, a TreeMap) is wrapped with DefaultObjectWrapper and then a 1 character long string is get from it that doesn't exist. To fix the issue, if the wrapped Map is a SortedMap and it's wrapped by DefaultObjectWrapper, it won't try to fall back to a Character key after with the String key has got null. (This change should be backward compatible, because when a SortedMap has Character keys, the initial attempt with String key causes ClassCastException, thus, such SortedMap-s were never usable as FTL hashes.)

    • Bug fixed [368 ]: Only with incompatible_improvements set to 2.3.22 or with its new useAdaptersForContainers setting set to true: Key order and other behavioral peculiarities of "custom" Map types isn't lost anymore. The same stands for List-s too.

    • Added new setting, forceLegacyNonListCollections. This only matters when useAdaptersForContainers is true. Then, unless you set this to true, java.util.Collection-s that aren't List-s (like Set-s) will continue using SimpleSequence (i.e., the copying approach) instead of the adapter approach. The default is false, at least until incompatible_improvements 2.4.0, because SimpleSequence gave indexed access to these non-List-s, like in mySet[2], which is strange but some existing templates may utilize this, even if only accidentally. With forceLegacyNonListCollections set to false, indexed access won't be possible for Set-s and such anymore (nor will ?first and ?last work, but ?size will still do), so you may want to retest old templates. On the other hand, you get the advantages of the adapter approach. Hence, in new projects it's highly recommended to set forceLegacyNonListCollections to false. (The adapter approach is implemented by DefaultNonListCollectionAdapter.)

    • Added new, experimental FTL type interface, freemarker.template.TemplateCollectionModelEx, which adds the size(), isEmpty(), and boolean contains(TemplateModel) methods to the TemplateCollectionModel interface. This was added because when wrapping java.util.Collections these extra capabilities area available anyway, but FTL couldn't tap on them till now. While the exact interface details are marked as experimental, the feature itself is already utilized for ?size when setting the forceLegacyNonListCollections property of DefaultObjectWrapper to false (see earlier).

    • Added new experimental interface, freemarker.template.ObjectWrapperAndUnwrapper. This extends ObjectWrapper with unwrapping functionality. This functionality has already existed for a long time in BeansWrapper and its subclasses, like in DefaultObjectWrapper, but it wasn't "factored out" into its own published interface that other ObjectWrapper-s could implement. This is useful for TemplateModel implementations that don't want to require a BeansWrapper (or its subclass), only the availability of the unwrapping functionality.

    • Added new experimental interfaces to implement ?api (see it in the FTL section): TemplateModelWithAPISupport, ObjectAPIWrapper, RichObjectWrapper. Note that while the interfaces are experimental, ?api itself isn't.

  • FreemarkerServlet improvements:

    • FreemarkerServlet now supports custom JSP EL functions (defined in TLD-s with function XML elements). Earlier it has ignored them. The custom EL function can be called like a Java method, for example: <#assign u=JspTaglibs["/WEB-INF/utils.tld"]> ... ${u.truncate(title, 25)}.

    • Bug fixed: Error message was unhelpful when there was a type mismatch between the actual and the expected type of a custom tag parameter. This was a very frequent problem of users who call JSP taglibs from FTL (the typical "java.lang.IllegalArgumentException: argument type mismatch", without any FTL context). Now it's a proper error with explanation, solution tip, and FTL error position/quotation.

    • RFE resolved [113 ] [114 ]: FreemarkerServlet can now discover META-INF/**/*.tld-s that are visible for the class loader but aren't in WEB-INF/lib/*.jar-s. For this feature to be active, you must setup the extra TLD lookup with the MetaInfTldSources and/or ClasspathTlds FreemarkerServlet init-params (see the Java API documentation of FreemarkerServlet for the description of these). For example, if you run your application from Eclipse with an embedded Servlet container, and thus the tag library jar-s aren't on the standard locations but are in the classpath like any other dependencies, now you can just write:

      <init-param>
        <param-name>MetaInfTldSources</param-name>
        <param-value>classpath</param-value>
      </init-param>

      and then all the META-INF directories that are visible for the class loader will be searched for TLD-s.

    • MetaInfTldSources and ClasspathTlds can also be appended to or replaced by the values of Java system properties org.freemarker.jsp.metaInfTldSources and org.freemarker.jsp.classpathTlds, respectively. Thus one can adjust these in the Eclipse run configuration without modifying the web.xml. (See the Java API documentation of FreemarkerServlet for more.)

    • FreemarkerServlet now recognizes the org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern servlet context attribute, and adds entries to MetaInfTldSources (introduced above) from it.

    • Added protected FreemarkerServlet.createTaglibFactory() to allow fine tuning the settings of the TaglibFactory. It now have a few setters, like setObjectWrapper, setMetaInfTldSource, etc.

    • Added new servlet init-param, BufferSize. This sets the buffer size via HTTPServletResponse.setBufferSize() if the response state still allows that, ignores it otherwise.

    • The TemplatePath servlet init-param now supports a new kind of path, that looks like classpath:com/example/myapp/templates. This is similar to the old class://com/example/myapp/templates, but it uses the Thread Context Class Loader of the thread that initializes FreemarkerSerlvet, and thus will work even if freemarker.jar is not local to the web application. class:// has the problem that it uses the defining class loader of FreemarkerSerlvet itself (or of its subclass).

    • If incompatible_improvements is set to 2.3.22 (or higher), the TemplatePath servlet init-param supports specifying multiple comma separated paths inside [...], like <param-value>[ WEB-INF/templates, classpath:com/example/myapp/templates ]</param-value>. This internally creates a freemarker.cache.MultiTemplateLoader.

    • Added new servlet init-param, ExceptionOnMissingTemplate. Setting this to true changes the behavior on template-not-found errors to similar to what you experience with other kind of template exceptions (a HTTP 500 "Internal Server error" response on most setups). When it's false (the legacy behavior), you only get a HTTP 404 "Not found". While that's also how JSP views work, this turns out to be a problem, because some frameworks give 404 to the visitor too if the MVC view gives 404. But to get to the point where you forward to the MVC View, the visitor had to visit a valid URL, only that page misses its View, so its broken on the server side, so it should be a 500.

    • Added new overridable method: FreemarkerServlet.createDefaultObjectWrapper(). This can be used for what createObjectWrapper() is usually overridden for, but without unwillingly disabling the processing of the related init-params (like of object_wrapper).

    • Improved (or fixed) error logging: Now logs will always get into FreeMarker's own log, not only into the servlet container log. Also, earlier template-not-found and template parsing error details logs were sometimes lost, depending on the servlet container.

    • Bug fixed, only active with incompatible_improvements set to 2.3.22 (or higher): Some kind of values, when put into the JSP page scope (via #global or via the JSP PageContext API) and later read back with the JSP PageContext API (typically in a custom JSP tag), might come back as FreeMarker TemplateModel objects instead of as objects with a standard Java type. Other Servlet scopes aren't affected. It's highly unlikely that something expects the presence of this bug. The affected values are of the FTL types listed below, and to trigger the bug, they either had to be created directly in the template (like as an FTL literal or with ?date/time/datetime), or you had to use DefaultObjectWrapper or SimpleObjectWrapper (or a subclass of them):

      • FTL date/time/date-time values may came back as freemarker.template.SimpleDate-s, now they come back as java.util.Date-s instead.

      • FTL sequence values may came back as SimpleSequence-s, now they come back as java.util.List-s as expected. This stands assuming that the object_wrapper configuration setting is a subclass of BeansWrapper (such as DefaultObjectWrapper), but that's practically always the case in applications that use FreeMarker's JSP extension (otherwise it can still work, but it depends on the quality and capabilities of the ObjectWrapper implementation).

      • FTL hash values may came back as SimpleHash-es, now they come back as java.util.Map-s as expected (again, assuming that the object wrapper is a subclass of BeansWrapper).

      • FTL collection values may came back as SimpleCollection-s, now they come back as java.util.Collection-s as expected (again, assuming that the object wrapper is a subclass of BeansWrapper).

    • Bug fixed: Now *.tld files are searched in WEB-INF/ and in all its subdirectories recursively. Earlier they were only searched directly under WEB-INF/ and WEB-INF/lib/.

    • Bug fixed: Leading and trailing whitespace in TLD-s inside the name and tag-class elements is now removed.

    • Unwanted behavior fixed: In case multiple TLD-s map to the same tag library URI, now WEB-INF/**/*.tld-s has priority over META-INF/**/*.tld-s coming from jar-s or classpath directories. Earlier, it was the other way around, except that META-INF/lib/*.tld-s could still take precedence randomly. While the JSP specification (2.2) explicitly states that the order is not defined and shouldn't be relied upon, it's just logical that if someone puts a TLD directly under WEB-INF, he meant that to be used in that particular web application, rather than the TLD-s coming from the dependency jars which are often shared by multiple web applications.

    • Bug fixed: Defaults set in an overridden FreemarkerServlet.createConfiguration won't be accidentally overwritten by FreemarkerServlet's factory defaults anymore. This was a problem with theses settings only: template_exception_handler, log_template_exceptions, object_wrapper, template_loader.

    • Bug fixed: If you had multiple FreemarkerServlet-s with different configuration settings in the same servlet context, that could lead to malfunction. (Normally, you only have one, just like there's only one servlet that processes *.jsp.)

    • Removed all the xsd files (web-app and taglib schemas) from the FreeMarker artifact and from the XML entity resolver, as they were unused during XML parsing.

    • Generally improved implementation quality (maintainability, error messages, performance bug fixes, test coverage) and better API documentation.

  • Logging facility improvements:

    • Just like earlier, when auto-selecting the logger library (the default behavior), FreeMarker choses Log4j if it's available. But now, if that turns out to be log4j-over-slf4j, FreeMarker will use SLF4J directly instead. (This fixes the issue where the logged location points to FreeMarker's log adapter class instead of the real call place.)

    • FreeMarker now recognizes the org.freemarker.loggerLibrary system property, which specifies which logger to use, like java ... -Dorg.freemarker.loggerLibrary=SLF4J. This option deprecates Logger.selectLoggerLibrary(int) as that was inherently unreliable (because you usually can't control class initialization order very well). The system property has precedence over Logger.selectLoggerLibrary.

    • Generally improved implementation quality (more info printed when something fails, etc.).

    • New configuration setting: log_template_exceptions (Configuration.setLogTemplateExceptions(boolean)). This specifies if TemplateException-s thrown by template processing are logged by FreeMarker or not. The default is true for backward compatibility, but that results in logging the exception twice in properly written applications, because there the TemplateException thrown by the public FreeMarker API is also logged by the caller (even if only as the cause exception of a higher level exception). Hence, in modern applications it should be set to false. (Note that this setting has no effect on the logging of exceptions caught by #attempt/#recover; those are always logged.)

  • Environment and custom directive related improvements:

    • Added Environment.getCurrentDirectiveCallPlace(), which returns a DirectiveCallPlace object when called from a custom directive (i.e., from TemplateDirectiveModel.execute()). The DirectiveCallPlace objects lets you associate an arbitrary object to the directive invocation inside the template, which can be used for call-place-bound caching (like the minification of non-dynamic nested content). See DirectiveCallPlace in the Java API documentation for more.

    • Added Environment.getMainTemplate(). Deprecated the ambiguous (and often broken: [145 ]) Environment.getTemplate().

  • Template loading:

    • Added new Configuration setting, template_lookup_strategy (Configuration.setTemplateLookupStrategy(TemplateLookupStrategy)). This allows customizing what TemplateLoader-level names will be tried when a template is requested. With this you can, for example, define a custom localized lookup sequence instead of the default (which looks like: foo_de_LU_MAC.ftl, foo_de_LU.ftl, foo_de.ftl, foo.ftl).

    • Added new Configuration.getTemplate(...) parameter, Object customLookupCondition. This parameter can be used by custom a TemplateLookupStrategy to deduce the actual template name(s) from the requested name (similarly to as the default lookup strategy does that based on the locale). For example, on a multi-domain Web site, one may want to define some templates that are specialized to a domain, and thus use the domain name as the custom lookup condition. Then, when foo.ftl is requested, a custom TemplateLookupStrategy could first look for @somedomain.com/foo.ftl, and then for @default/foo.ftl. See the JavaDoc of the relevant Configuration.getTemplate(...) overload for more details; note there the requirements regarding the hashCode and equals of the customLookupCondition.

    • Added new Configuration setting, template_name_format (Configuration.setTemplateNameFormat(TemplateNameFormat)). This allows specifying the naming rules used by FreeMarker. For now, custom implementations aren't allowed, and you can only chose between TemplateNameFormat.DEFAULT_2_3_0 (the default) and DEFAULT_2_4_0 (recommended, at least for new projects). DEFAULT_2_4_0 has several advantages, but isn't fully backward compatible (though most applications won't be affected). For typical mistakes like using backslash instead of slash, or backing out of the root, it gives MalformedTemplateNameFormatException instead of TempalteNotFoundException. It allows scheme names to be terminated with : alone, instead of a :// (which is also supported), like in classpath:foo/bar.ftl. It fixes numerous legacy glitches (bugs), mostly related to the interpretation of .. after special steps like . or *. See the full list of differences in the Java API documentation of TemplateNameFormat.DEFAULT_2_4_0 .

    • ClassTemplateLoader now can be created by specifying a ClassLoader directly, rather than by specifying a base Class. That is, now there's ClassTemplateLoader(ClassLoader, String) constructor, and also a Configuration.setClassLoaderForTemplateLoading(ClassLoader, String) method.

    • Added new exception, TemplateNotFoundException, which is now used instead of TemplateNotFoundException when getting a template. As it extends TemplateNotFoundException, this change is backward compatible. The main goal was to counter the common misunderstanding that template paths are real file paths. However, the new exception also has the benefit that it can give additional FreeMarker-specific information about the error, like right now it has getTemplateName() and getCustomLookupCondition() methods.

    • Template-s now have a getSourceName() method, in additionally to getName(). These two return the same as far as no localized lookup or acquisition (* in the name) or other lookup strategy was actively involved. But when it was, getSourceName() gives the name with which the template was actually loaded from the TemplateLoader, while getName() returns (and had always returned) the name with which the template was requested (in canonicalized form). getName() is used for everything (like for relative inclusion resolution), except for location information in error messages, which now uses getSourceName(). Also, TemplateException now has a getSourceName() method.

    • Configuration.getTemplate(...) overloads now accept null for the locale and encoding parameters, in which case they use the same defaults as the overloads where the parameter is omitted.

    • Debugger SPI implementators, attention: The DebugBreak instruction will now send the sourceName of the template to the suspendEnvironmentSpi callback, rather than its name. You should also use the sourceName in registerTemplateSpi and such, not the name.

  • Configuration:

    • Added Configuration.unsetXxx and isXxxExplicitlySet methods for several settings. Unsetting a setting makes it behave as if setXxx was never called, thus the setting will use the default value that fits the current incompatible_improvements value and will be adjusted as incompatible_improvements is changed later.

    • When configuring FreeMarker from java.util.Properties (or with String-String name-value pairs in general):

      • The default setting value is now recognized by template_exception_handler, template_storage, template_loader (and by the new template_lookup_strategy and template_name_format) settings, and it causes Configuration.unsetXxx() to be called.

      • Bug fixed: When setting object_wrapper to default (as opposed to not specifying it), it has ignored the incompatible_improvements and has always used ObjectWrapper.DEFAULT_WRAPPER. This fix only matters when incompatible_improvements is exactly 2.3.21, as that's when the default object wrapper was changed from ObjectWrapper.DEFAULT_WRAPPER to the result of new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_21).build(), which is a bit different singleton, as it has read-only configuration settings and bug fixed overloaded method selection rules. To use ObjectWrapper.DEFAULT_WRAPPER regardless of the value of the incompatible_improvements setting, use the new default_2_3_0 value.

    • Bug fixed: Changing the value of the localized_lookup setting now empties the template cache, so that old lookup results won't be reused. (This of course only matters if you change this setting under an already running service, which is very unlikely.)

  • Miscellaneous:

    • Bug fixed [145 ], active only with incompatible_improvements set to 2.3.22 (or higher): #include and #nested doesn't change the parent Template (see Configurable.getParent()) of the Environment anymore to the Template that's included or where #nested "returns" to. Thus, the parent of Environment will be now always the main Template. (The main Template is the Template whose process or createProcessingEnvironment method was called to initiate the output generation.) Note that this only matters if you have set settings directly on Template objects (not to be confused with setting settings in templates via #setting, which just modifies the Environment, and so isn't affected by this fix), and almost nobody does that. Also note that macro calls have never changed the Environment parent to the Template that contains the macro definition, so there's no change there now.

    • Bug fixed [419 ]: FreeMarker doesn't fail anymore when it has no permission to read Java system properties, like when used in unsigned applets. It just logs some warnings.

    • HTML_DEBUG and DEBUG TemplateExceptionHandler output now contains a warning like "HTML_DEBUG mode; use RETHROW in production!", due to frequent misuse.

    • Some fixes and improvements in template canonical form output, and as a consequence of that, in FTL stack trace instruction displaying.

    • Marked some historically public but otherwise internal API-s as deprecated, so that the disclaimer is more apparent in IDE-s.

Notes

The consequences and reasons of introducing adapter approach for container types in DefaultObjectWrapper when its incompatibleImprovements is set to 2.3.22:

  • With the new approach (the adapter approach), the key order of Map-s is never lost. The copying approach could only keep that for HashMap subclasses (such as LinkedHashMap) and SortedMap-s (such as TreeMap), but not for more exotic Map-s, like Guava's ImmutableMap. Also, any other behavioral peculiarities of the original Map (e.g., case insensitive key lookup) is kept now.

  • The exact type and identity of the Map/List is kept when the wrapped value is passed back to a Java method from the template. With the legacy approach the Java methods have received a Map or List of a special FreeMarker specific type (that acted as an adapter for the TemplateModel).

  • Performance characteristics change, mostly for the better, but it depends on the application. If the template reads the same(!) entry from the data model roughly once or twice (or not at all), which is typical, them the adapter approach gives better results, otherwise the legacy copying approach is faster (as it can reuse the wrapped entry from the previous read), though this slowdown certainly not a concern for most applications. The performance of the new adapter approach is more predictable, because it has no initial "spike" to set up the container copy (especially painful for huge collections), instead the performance is linearly proportional to the number of data model reads (and not to the number of collection entries).

  • If the Map/List/array is changed after it was wrapped, the change will now become visible in the data-model. With the copying approach, the wrapped value was a shallow-snapshot of the original Map/List/array. While it's unlikely that someone has deliberately utilized this, it's a risk factor when switching to adapters.

  • It's theoretically possible that some code (mostly TemplateDirectiveModel implementations) mistakenly assumed that wrapped Map-s are SimpleHash-es, and wrapped List-s are SimpleSequence-s, etc., instead of them just being TemplateHashModel-s and TemplateSequenceModel-s. Such code was always wrong, but now it will indeed break, so it's a risk factor.

  • As now the exact type of the wrapped original object is used for overloaded method selection, the choice can be different (and similar to what it would be with pure BeansWrapper). It's difficult to find cases where this matters. A change is most probable around arrays, as with the copying approach they were unwrapped to List-s, not to the original array. As the overloaded method mechanism can convert between arrays and lists (in both directions), it's usually not a problem. But, it doesn't do conversion between different array types when the overloaded method has various types on the parameter position of the array, so that's a risk factor.

  • SimpleHash and SimpleSequence haven't become deprecated. They are still used for hashes and sequences created in FTL, and are recommended for values that are built specifically to be used from templates, rather than wrapping an already existing Map or List or array.

  • List-s and Map-s that are exposed to templates in multiple threads are now under greater stress regarding their correct operation under multi-threaded read-only access. This is because the adapters won't copy their contents into well known List and Map implementations (HashMap, ArrayList, etc.) before accessing them from multiple threads. So this is mostly a concern with custom List and Map implementations, which aren't as mature as the standard Java classes. Note that this was always like so with pure BeansWrapper, which is used by a lot of projects/frameworks (like by Struts) for a long time, so it's not an uncharted territory.

  • When the wrapped List is a AbstractSequentialList (like a LinkedList), the resulting adapter will implement TemplateCollectionModel for more efficient enumeration (#list-ing), in additionally to TemplateSequenceModel of course. TemplateCollectionModel allows FTL to traverse the list without accessing elements by index. With the legacy copying approach TemplateCollectionModel wasn't implemented as it wasn't needed for efficient enumeration there.

  • Iterators (when you put them directly into the data-model) are wrapped into DefaultIteratorAdapter instead of SimpleCollection. This has two consequences:

    • The wrapped Iterator is now unwrapped properly to the original Java object when it's passed to Java method from the template.

    • Wrapped Iterator-s (not to be confused with Iterable) aren't thread-safe anymore, to spare some synchronizations, after all, exposing the same Iterator to multiple parallel template executions doesn't make much sense. This shouldn't be a migration concern, as even earlier, only one of those template executions could succeed (the "content" of Iterator-s wasn't copied, so the one who first accessed it become the exclusive owner). The change is just that earlier it was guaranteed that the other threads will fail (that was the thread-safe about it), while now there are no such guarantees.