freshmarker logo3

Introduction

FreshMarker is a simple, embedded Java 21 Template-Engine, which is inspired by FreeMarker. Most of the basic concepts are the same.

FreshMarker's area of application is the processing of simple templates with reduced programming features. Despite reduced programming features, FreshMarker is open to new data types and built-ins. In particular, the lack of modern data types like java.time.LocalDate in FreeMarker was the reason for the implementation of FreshMarker.

Adding FreshMarker

Add FreshMarker as a dependency from Maven Central to your project.

Maven
<dependency>
    <groupId>de.schegge</groupId>
    <artifactId>freshmarker</artifactId>
    <version>2.0.0-SNAPSHOT</version>
</dependency>
Gradle
implementation 'de.schegge:freshmarker:2.0.0-SNAPSHOT'
Gradle Kotlin
implementation("de.schegge:freshmarker:2.0.0-SNAPSHOT")
Mill
ivy"de.schegge:freshmarker:2.0.0-SNAPSHOT"

Usage

Using the template engine is very easy. First the configuration has to be generated and then a template instance has to be created with its help. Finally, the template is processed with the data from the model. The return value of the process method is the result of template processing.

Since version 1.5.0, the Template is no longer created directly via the Configuration, but via the DefaultTemplateBuilder.

Example Engine Call
Configuration configuration = new Configuration();
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", "Hello ${example}!");
System.out.println(template.process(Map.of("example", "World")));
Example Engine Call (before 1.5.0)
Configuration configuration = new Configuration();
Template template = configuration..getTemplate("test", "Hello ${example}!");
System.out.println(template.process(Map.of("example", "World")));

There are a few design decisions to consider when using the template engine.

The configuration is complex to generate and should therefore not be generated for each template.

There are many ways in your own application to provide the configuration only once. A simple Spring Boot variant is shown in the example.

Spring Boot Integration
@Bean
public Configuration freshmarker() {
    return new Configuration();
}

The model should be static, the values should not change during processing. This can lead to surprising results.

Templates

A template engine generates output by creating a representation of a model using instructions in a template.

This complicated sentence can be better explained with a small example.

Example Template
Hello ${example}!

The example template contains three parts. A first literal text fragment Hello , an interpolation fragment ${name} and a second literal text fragment !. Literal text means the fragment is not interpreted in any particular way [1].

Interpolations

The interpolation is a fragment, which is filled with data from the model. The name example in the curly brackets is the name by which a value in the model can be identified.

Example Model
Map<String, Object> model = Map.of("example", "World");

The example model contains a value World with the name example.

When the example template is processed, the template engine produces the output Hello World! based on the example model.

Directives

In addition, there is the possibility of hiding fragments in the template or repeating them several times. There are special constructs for this, called directives.

Example Template with directive
<#if example=='World'>
Hello ${example}!
<#else>
Hallo ${example}!
</#if>

Directives look like HTML tags with a # before the tag name. In this example, the English Hello is only written if the value is World. In all other cases the German Hallo is written.

Built-Ins

Another important template concept is the built-in. The result of an interpolation is changed by the built-ins. The interpolation from the last example produces the output World from the value World. If the interpolation is extended to ${example?upper_case} then the interpolation produces WORLD. In this case the built-in upper_case has been added. Depending on the type of value, a wide variety of built-ins can be used. [2].

User Directive

In addition to the standard directives, FreshMarker also supports User Directives.

Log User Directive
<@log level='info' message='test'/>

User directives can be defined via Macro directives or by implementing the UserDirective Interface.

UserDirective Interface
public interface UserDirective {

  void execute(ProcessContext context, Map<String, TemplateObject> args, Fragment body);
}

It can be registered to the Configuration by the registerUserDirective method or by the Extension API.

Example User Directive doubles
configuration = new Configuration();
configuration.registerFunction("doubles",
    (context, args, body) -> { body.process(context); body.process(context); }
);

Functions

Additional functionality can be added to the template-engine interpolations by Functions.

Function Interface
public interface TemplateFunction {

    TemplateObject execute(ProcessContext context, List<TemplateObject> args);
}

A Function is a Java class implementing the functional interface TemplateFunction. It can be registered to the Configuration by the registerFunction method or by the Extension API.

Example Custom Function abs and avg
configuration = new Configuration();

configuration.registerFunction("abs", (context, args) ->
    args.get(0).evaluateToObject(context).asNumber().map(TemplateNumber::abs).orElseThrow());

configuration.registerFunction("avg", (context, args) ->
    args.stream().map(o -> o.evaluate(context, TemplateNumber.class)).reduce(TemplateNumber::add).orElseThrow().divide(new TemplateNumber(args.size())));

Each registered Function can be called via a function call with its name as the function name.

Example Usage of Custom Function
<#-- result 15 -->
${avg(10, 20)}

<#-- result 25 -->
${avg(10, 20, 30, 40)}

<#!-- result 10 -->
${abs(-10)}

Output Format and Escaping

Every Template is associated with an Output Format. The Output Format defines the escaping rule for dynamically generated output. Without escaping, there is a security risk known as template injection. With template injection, dangerous fragments are injected into the output.

Example Template Injection
<h1>${title}</h1>
Model with Javascript Injection
Map<String, Object> model = Map.of("title", "<script>alert("hallo")</script>");
HTML Output
<h1>&lt;script&gt;alert(&quot;hallo&quot;)&lt;/script&gt;</h1>
Plain Text Output
<h1><script>alert("hallo")</script></h1>

The example shows an HTML fragment in which the text for a heading is to be inserted. Without escaping, the script fragment is inserted and a more or less dangerous Javascript is executed instead of displaying a title.

FreshMarker's escape mechanism replaces the necessary meta characters of an output format to neutralize them. The following table lists the supported output formats and their meta character replacements. the last two lines indicate how comments can be inserted into the output using the @log directive. The character is used to indicate whitespaces in prefixes and suffixes.

Table 1. Output Formate
Name Meta Characters Replacements Comment Prefix Comment Suffix

HTML

<, >, &, ", '

&lt;, &gt;, &amp;,&quot;, &#39;

<!--␣

␣-->

XHTML

<, >, &, ", '

&lt;, &gt;, &amp;,&quot;, &#39;

<!--␣

␣-->

XML

<, >, &, ", '

&lt;, &gt;, &amp;,&quot;, &apos;;

<!--␣

␣-->

ADOC

no escaping

////\n

\n////\n

plainText

no escaping

no comments

Script

no escaping

/*␣

␣*/

JavaScript

no escaping

/*␣

␣*/

CSS

no escaping

/*␣

␣*/

JSON

no escaping

no comments

Custom output formats can be registered to the Configuration by the registerOutputFormat method or by the Extension API.

Template Loading

The Configuration (DefaultTemplateBuilder since 1.5.0) class offers four methods for loading templates.

Table 2. Template Loading Methods
Method Description

Template getTemplate(Path path);

Load template from path with default charset

Template getTemplate(Path path, Charset charset);

Load template from path with the given charset

Template getTemplate(String name, Reader reader);

Read template from the given reader

Template getTemplate(String name, String content);

Read template from the given string

Template getTemplate(Path importPath, String name, Reader reader);

Read template from the given reader

Template getTemplate(Path importPath, String name, String content);

Read template from the given string

Regardless of the method selected for loading the template, the imports are loaded by the configured TemplateLoader.

By default, the DefaultFileSystemTemplateLoader is configured. This loads all imports from the file system.

The FileSystemTemplateLoader can also be used with other file system implementations.

TemplateLoader with the ZipFileSystem
FileSystem fs = FileSystems.newFileSystem(Paths.get("imports.zip"));
configuration.setTemplateLoader(new FileSystemTemplateLoader(fs));

In this example, the imports are loaded from the zip archive imports.zip.

Custom template loaders can also be used instead of the DefaultFileSystemTemplateLoader. They have to implement the TemplateLoader interface.

public interface TemplateLoader {
    String getImport(Path path, String filename, Charset charset) throws IOException;

    default String getImport(Path path, String filename) throws IOException {
        return getImport(path, filename, Charset.defaultCharset());
    }
}

The filename parameter is the name of the import file and path is the parent directory of the template. The parent directory can be used to load import files located relative to the template.

For templates loaded by String or Reader the path is the current directory.

Template Caching

FreshMarker does not know any caching mechanism, but you can store and reuse templates with any technology (Maps, Singletons, Caches) you like.

Partial Reduction

This feature is in an experimental stage and should be used with care.

When processing a template, all necessary data must be available in the model. Otherwise, an exception is thrown if data is missing during processing. If the template contains values that remain the same for a large number of evaluations, the template can be preprocessed.

Example Template with reduction on company
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", """
   <#list employees as e>${e.firstname}.${e.lastname}@${company.domain}</#list>
   """);
Map<String, Object> reductionMap = Map.of("company", new Company("schegge.de"));
Template reducedTemplate = template.reduce(reductionMap);

The reduce method receives some of the necessary data and partially evaluates the template. As a result, it delivers a new template in which part of the structure has been simplified.

Although the replaced variables do not have to be passed to the process method later, it is recommended to make them available anyway. If a variable could not be replaced completely by Partial Reduction, there will otherwise be errors.

Best Practices with Partial Reduction Data
Map<String, Object> reductionMap = Map.of("company", new Company("schegge.de"));

Template reducedTemplate = template.reduce(reductionMap);

List<Employee> employees = List.of(new Employee("Jens", "Kaiser"));

Map<String, Object> processMap = new HashMap<>();
processMap.put("employees", employees);
processMap.putAll(reductionMap);

String content = reducedTemplate.process(processMap);

Partial Reduction works at the level of directives and interpolations. The reduction is performed differently depending on the type of directive.

The special nature of the Default and Exist Operators

Partial Reduction evaluates all expressions based on the available data. Where the evaluation can be carried out in full, a reduction is possible; where the evaluation runs into an error because data is missing, no reduction can take place.

The default and exist operators have a special quality. Their mode of operation includes the reaction to non-existent data. Therefore, only their reactions to existing data can be taken into account in the reduction. The missing values are only not specified in the reduction, the normal evaluation can provide these values later. Therefore, evaluating NULL values during reduction is considered an error.

Reducing Interpolations

Partial reduction replaces interpolations with text fragments if the expression can be fully evaluated within the interpolation.

Example Template with reduction on If Direktive
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", """"
   ${company.domain?upper_case}
   """);
Template reducedTemplate = template.reduce("company", new Company("schegge.de"));

In this example, the interpolation is replaced by the text SCHEGGE.DE.

Reducing Conditionals

The conditional directives If and Switch are reduced by replacing their fragment with the block whose condition is fulfilled. If an error occurs during the evaluation of a condition because data is missing, the conditional directive is not replaced.

Example Template with reduction on If Direktive
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", """"
   <#if company.domain??>
   ${e.firstname}.${e.lastname}@${company.domain}
   <#else>
   No email address
   </#if>
   """);
Template reducedTemplate = template.reduce("company", new Company("schegge.de"));

In this example, the If Directive can be replaced by ${e.firstname}.${e.lastname}@schegge.de because the expression company.domain?? could be evaluated.

List Unrolling (since 1.10.0)

Partial Reduction on List Directives is executed by unrolling the loops. Unrolling is used by default only if there is a maximum of five entries in the list.

This feature has to be activated with ReductionFeature.UNFOLD_LIST. See Partial Reduction Features how to change the maximum of five entries for unrolling.

Model

The template engine needs data to fill the interpolations. Because it is an embedded Java engine, normal Java classes are used. A map is always used as the model for calling the engine.

Types

By default, the template engine supports String, Number like Integer, Long, Double and Float, Boolean and Date Types as base types. In addition, the engine also knows List, Map, Sequences, Records and Beans.

The top level model is a Map<String, Object>, which contains the top level values by their names.

Example Model
Map<String, Object> model = Map.of("father", father, "mother", mother);

The following tables show all model types supported by FreshMarker. Because FreshMarker can be extended, there are more types available [3].

Without a package specification, the class names refer to the classes of the same name from the JDK.

Table 3. Supported Model Types
Type Java Type(s) FreshMarker primitive type

String

String, StringBuilder, StringBuffer

yes

String

UUID, URI, URL

yes

Number

BigInteger, Long, Integer, Short, Byte

yes

Number

long, int, short, byte, double, float

yes

Number

BigDecimal, Double, Float

yes

Number

AtomicLong, AtomicInteger

yes

Boolean

Boolean, boolean, AtomicBoolean

yes

Character

Character, char

yes

DateTime

Instant, ZonedDateTime, OffsetDateTime, LocalDateTime`, Date

yes

Date

LocalDate, java.sql.Date

yes

Time

LocalTime, java.sql.Time

yes

Year

Year

yes

YearMonth

YearMonth

yes

MonthDay

MonthDay

yes

Duration

Duration

yes

File

File

yes

Path

Path

yes

Period

Period

yes

Sequence

Lists

no

Range

-

no

Slice

-

no

Map

Maps

no

Bean

Beans

no

Record

Top-Level Records

no

Enum

Enums

yes

Null

-

yes

Locale

Locale

yes

Version

-

yes

Random

Random, SecureRandom

yes

The types File, Path and Random are part of the FreshMarker File respectively FreshMarker Random

Primitive Types

FreshMarker distinguishes between primitive and non-primitive types. All primitive types, except Null, have a default representation via their toString method.

This must be considered with the enums, because the toString method can be overridden.

Other types, such as ranges, slices, sequences and hashes are only permitted in expressions.

Literals of primitive types can be used for Null, String, Boolean, Integer and Double in the templates.

Type

Examples

Null

null

String

'test', '', "test", ""

Integer

1234, 42

Double

12.34, 42.0

Sequences

Sequences represent all data structures that implement java.util.List.

The elements of sequences can be accessed via the hash operator sequence[index] or the slice operator.

Example Sequence hash operator
sequence[42]
sequence[index] //  index is a variable coatining the number 42

The expression index can be a value between 0 and the length of the sequence. Other values lead to an error.

It is possible to use literals of sequences in templates, whereby the elements are written comma-separated between square brackets.

Example Sequence literals
['a','b','c']
[1,3,5,7,9]

Hashes

Hashes represent data structures in which a data element is accessed via a String key. This includes all implementations of java.util.Map that define a String key. FreshMarker also supports Java Beans and Records as Hash. In this case, the data elements are accessed via the attribute names. The elements can be accessed via the dot operator hash.name or the hash operator hash['name'].

Example Hash dot operator
hash.value
hash['value']
hash[key]      // key is a variable coatining the string 'value'

It is possible to use literals of hashes in templates, whereby the key-value pairs are written comma-separated between curly brackets.

Example Hash literals
{ 'firstname': 'Jens', 'lastname': 'Kaiser' }
{ 'boolean': true, 'integer': 42 }

Ranges

Ranges are data structures that represent an interval with a lower limit and an optional upper limit.

Ranges without an upper limit are called right-unlimited ranges, ranges with an upper limit are called right-limited ranges. If the size of the interval is specified instead of the upper limit, then this range is called a length-limited range.

Ranges differ from other types because they only exist as literals in the templates. Ranges cannot be injected via the model.

Right-unlimited Range

In principle, a right-unlimited range has no upper limit and can therefore only be used in a few places.

The main area of application is slicing, where a part is cut out of a list, String or range. 3 A right unlimited range has the form x.. where x can be any numerical expression.

Example right-unlimited ranges
1..   // lower bound 1
-10.. // lower bound -10
a..   // lower bound in the variable a (must be a numeric value)

Right-limited Range

A right-limited range has a lower and an upper limit. It also represents a sequence of numbers and can be used in this way in a list directive. A right-limited range has the form x..y or x..<y where x and y can be any numeric expression. The second form describes a range with an exclusive upper limit. This form is called a right-limited exclusive range.

Example right-limited ranges
1..10   // lower bound 1 and upper bound 10
-10..<0 // lower bound -10 and upper bound -1
a..b    // lower bound in variable a and upper bound in variable b
10..0   // lower bound 1 and upper bound 10
a..<b   // lower bound in variable a and an exclusive upper bound in variable b

As can be seen in the examples, the lower limit may be greater than the upper limit. This means that the representation as a sequence cannot be monotonically increasing, but can be monotonically decreasing. In the case of a monotonically increasing sequence, we speak of a legal range, otherwise of an inverse range.

Length-limited Range (since 1.6.4)

A length-limited range is a right-limited range with a different syntax a..*c. Where a again corresponds to the lower limit of the range and c indicates the size of the range.

If the size of the range is positive, then the upper limit is greater than the lower limit. This results in a legal right-limited range. If the size of the range is negative, then the upper limit is smaller than the lower limit. This results in an inverted right-limited range.

Example length-limited ranges
1..*10  // lower bound 1 and upper bound 10
5..*5   // lower bound 5 and upper bound 9
4..*-5  // lower bound 4 and upper bound 0
a..*b   // lower bound in variable a and an range size in variable c

Slices

Slices define slice operators on other data types. These operators can be applied to Range, Sequence, String and Number values. The slices are described by range expressions in square brackets. The range specifies the lower and upper limits of the Slice. With inverted ranges, the slice operation produces an inverted result.

Empty Slices

Slices based on empty ranges can also be used for slicing. However, they only ever generate an empty object of the respective target type. This means an empty Range, an empty Sequence, an empty String and NULL as an empty Number.

List Slices

The slice operator on List values extracts a sublist from a given list. The lower and upper bounds are the index in the source list for the first and last entry of the new list. For slices without an upper bound, the last entry of the source list is the last entry of the new list.

Because the first entry of a list is zero in the Java Universe, a slice with a lower bound below zero is forbidden.

In the case of a right-limited range, the upper bound must exist in the source list. Otherwise, a processing error will occur.

Example List slices
${['a','b','c','d','e','f','g'][1..4]?join(';')} // => b:c:d:e
${['a','b','c','d','e','f','g'][1..]?join(';')}  // => b;c;d;e;f;g
${['a','b','c','d','e','f','g'][4..1]?join(';')} // => e;d;c;b

To imitate the behavior of a Java sublist with an exclusive upper bound, a right-limited range with an exclusive upper bound can be used.

Example List slice with exclusive upper bound
${['a','b','c','d','e','f','g'][1..<5]?join(';')} // => b:c:d:e

Range Slices

The slice operator on Range values extracts a range form a given range. For slices without an upper bound, the upper bound of a limited source range is the upper bound of the new range.

Example Range slices
${(1..10)[1..4]?join(';')} // => 2;3;4;5
${(1..10)[1..]?join(';')}  // => 2;3;4;5;6;7;8;9;10
${(1..10)[4..1]?join(';')} // => 5;4;3;2

String Slices

The slice operator on String values extracts a substring from a given string. The lower and upper bounds are the index in the source string for the first and last character of the new string. For slices without an upper bound, the last character of the source string is the last character of the new string.

Because the first index of a string is zero in the Java Universe, a slice with a lower bound below zero is forbidden.

In the case of a right-limited slice, the upper bound must exist in the source string. Otherwise, a processing error will occur.

Example String slices
${'abraxas'[1..4]} // => brax
${'abraxas'[1..]}  // => braxas
${'abraxas'[4..1]} // => xarb

To imitate the behavior of a Java substring with an exclusive upper bound, a right-limited range with an exclusive upper bound can be used.

Example String slice with exclusive upper bound
${'abraxas'[1..<5]} // => brax

Optional Values

Optional values of type java.util.Optional are also supported in the model. Empty optionals are interpreted as NULL and all other values are interpreted as instances of the generic type.

An optional can therefore be checked with the existence operator and the default operator can be used to determine a replacement value for empty optionals.

Java FreshMarker

optional.isPresent()

${optional??}

optional.isEmpty()

${optional == null}

optional.orElse(42)

${optional!42}

Lazy Values

Normally, the values are written directly into the model. Sometimes the cost of creating optional values is quite high. In that case, it would be a waste to calculate the values if they are not used. However, since FreshMarker needs the values in the model, a compromise is made. Instead of calculating the values immediately and placing them in the map, the calculation is inserted into the model as a TemplateObjectSupplier. Their actual values are lazy evaluated only when accessed during processing.

Example lazy values
Map<String, Object> model = Map.of(
  "familyTree", TemplateObjectSupplier.of(() -> fetchTreeFromDataBase(name)),
  "name", "Kayser");
template.process(model);

In the example above, the familyTree value is lazy evaluated, when the variable is accessed during template processing.

Interpolations

Interpolations produce output for the values in the model.

When processing an interpolation, there are two mandatory constraints that must be met.

An interpolation must evaluate to a FreshMarker primitive type. For example, if the result is a list, an exception is thrown. If the result of the evaluation is the special value NULL, then an exception is also thrown. An interpolation must always return a result.

Example with Processing Exception
Configuration configuration = new Configuration();
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", "Hello ${example}!");
System.out.println(template.process(Map.of()));

The example above produces an ProcessException, because the interpolation ${example} is evaluated to NULL.

To deal with missing values, FreshMarker provides two operations. The default operator ! and the exists operator ??.

The default operator replaces a NULL value with the specified parameter. If the parameter is missing, the value is replaced with an empty text.

Example with default operator
Configuration configuration = new Configuration();
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", "Hello ${example!'World'}!");
System.out.println(template.process(Map.of()));

The example above produces the output Hello World!, because the interpolation ${example!'World'} evaluates to World if no value for example is given.

The exists operator evaluates to true if the value exists, and it evaluates to false if the value is missing.

Example with exists operator
Configuration configuration = new Configuration();
TemplateBuilder templateBuilder = configuration.builder();
Template template = templateBuilder.getTemplate("test", "Hello ${example???then(example, 'World')}!");
System.out.println(template.process(Map.of()));

The exists example is a bit more complicated because the operator is commonly used in directives.

Since there are enough examples for the exists operator in the directives section, this unusual variant is presented here.

The interpolation consists of the variable example, the exists operator ?? and the boolean build-in ?then(example, 'World'). The exists operator is called on the missing value example and the result is false. The boolean built-in returns the first parameter on true and the second parameter on false. In this example the second parameter 'World' is returned and the result of the whole interpolation is World.

Built-Ins

Built-Ins are interpolation operations which are called on the current value of the interpolation.

Example with upper_case built-in
TemplateBuilder builder = new Configuration().builder();
Template template = builder.getTemplate("test", "HELLO ${example?upper_case}!");
System.out.println(template.process(Map.of("example", "world")));

In the above example the built-in upper_case is called on the current value world of the interpolation. The built-in converts a text to its uppercase form.

Since the capitalization can only be changed for text, the execution of this built-in on other types such as numbers or dates is forbidden. In general, all built-ins are assigned to a type and an exception is thrown if a wrong type is used.

if a built-in seems to apply to multiple types, it’s because each type has a built-in with the same name.

It is possible to use more than one built-in in an interpolation. The built-ins are called from left to right on the current value of the interpolation. This means a built-in is called on the result of a previous built-in or the initial value.

Example with two built-ins
TemplateBuilder builder = new Configuration().builder();
Template template = builder.getTemplate("test", "HELLO ${example?upper_case?lower_case}!");
System.out.println(template.process(Map.of("example", "WorlD")));

In the example above the upper_case built-in is called on the current value WorlD. The second built-in lower_case is called on the current value WORLD, which is the result of upper_case. the result of the interpolation is the result world of the second built-in.

Null Aware Built-In Handling (since 1.7.4)

Normally, the processing of built-ins ends with an exception if no suitable built-in is found. In the case of NULL values and empty optionals, this behavior can now be changed using the flags BuiltinHandlingFeature.IGNORE_OPTIONAL_EMPTY and BuiltinHandlingFeature.IGNORE_NULL flags. In both cases, the actual built-in is ignored and the input value is returned.

Example with null-aware built-in handling
Configuration configuration = new Configuration();
TemplateBuilder builder = configuration.builder().with(BuiltinHandlingFeature.IGNORE_NULL);
Template template = builder.getTemplate("test", "Hello ${example?upper_case?lower_case!'World'}!");
assertEquals("Hello World", template.process(Map.of()));

The example does not generate an exception if example contains the value NULL, because both build-ins pass on the value instead of throwing an exception. A default operator must be added at the end so that the interpolation as a whole does not throw an exception.

Instead of setting both features, the nul-safe built-in operator ?! can be used.

TemplateBuilder builder = new Configuration().builder();
Template template = builder.getTemplate("test", "Hello ${example?!upper_case?!lower_case!'World'}!");
assertEquals("Hello World", template.process(Map.of()));

=== Log Built-In

The special built-in log can be used to inspect the current value in an expression. The built-in logs the current content of the expression to the logger builtin.logging and returns the current value.

Example log built-in
example?log?upper_case?log?lower_case?log

This expression produces the following log.

Example log output
20:25:05 [main] DEBUG builtin.logging -- input:1:3 'example' => World
20:25:05 [main] DEBUG builtin.logging -- input:1:3 'example?log?upper_case' => WORLD
20:25:05 [main] DEBUG builtin.logging -- input:1:3 'example?log?upper_case?log?lower_case' => world

=== String Built-Ins

==== upper_case Built-In

This built-in converts a text in its uppercase form.

Input Output

${'The quick brown Fox jumps over the lazy Dog'?upper_case}

THE QUICK BROWN FOX JUMPS OVER THE LAYZ DOG

==== lower_case Built-In

This built-in converts a text in its lowercase form.

Input Output

${'The quick brown Fox jumps over the lazy Dog'?lower_case}

the quick brown fox jumps over the lazy dog

==== capitalize Built-In

This built-in converts the first letter of each word to uppercase. A word with a first lowercase letter means \b(\p{javaLowerCase})(\p{IsAlphabetic}*)\b

Input Output

${'The quick brown Fox jumps over the lazy Dog'?capitalize}

The Quick Brown Fox Jumps Over The Lazy Dog

==== uncapitalize Built-In

This built-in converts the first letter of each word to lowercase. A word with a first uppercase letter means \b(\p{javaUpperCase})(\p{IsAlphabetic}*)\b

Input Output

${'The quick BROWN Fox jumps over the lazy Dog'?uncapitalize}

the quick bROWN fox jumps over the lazy dog

==== camelCase Built-In

This built-in converts a text from snake_case, screaming_snake_case or kebab-case to camelCase. The result may look strange if the input is in some other format.

Input Output

${'The-quick-brown-Fox-jumps-over-the-lazy-Dog'?camelCase}

theQuickBrownFoxJumpsOverTheLazyDog

${'The_quick_brown_Fox_jumps_over_the_lazy_Dog'?camelCase}

theQuickBrownFoxJumpsOverTheLazyDog

==== snake_case Built-In

This built-in converts a text from camelCase to snake_case. The result may look strange if the input is in some other format.

Input Output

${'theQuickBrownFoxJumpsOverTheLazyDog'?snake_case}

The_quick_brown_Fox_jumps_over_the_lazy_Dog

==== screaming_snake_case Built-In

This built-in converts a text from camelCase to screaming_snake_case. The result may look strange if the input is in some other format.

Input Output

${'theQuickBrownFoxJumpsOverTheLazyDog'?screaming_snake_case}

THE_QUICK_BROWN_FOX_JUMPS_OVER_THE_LAZY_DOG

==== kebab_case Built-In

This built-in converts a text from camelCase to kebab-case. The result may look strange if the input is in some other format.

Input Output

${'theQuickBrownFoxJumpsOverTheLazyDog'?kebab_case}

The-quick-brown-Fox-jumps-over-the-lazy-Dog

==== slugify Built-In (since 1.4.6)

This built-in converts a text to a slug.

Input Output

${'the short summer'?slugify}

the-short-summer

${'Die heiße Wüste'?slugify}

die-heie-wste

${'Die heisse Wueste'?slugify}

die-heisse-wueste

==== strip Built-In (since 1.11.0)

This built-in removes leading and trailing Unicode whitespaces[4] from the text. The examples use as a replacement for the whitespace.

Input Output

${'␣␣␣The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog␣␣'?strip}

The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog

==== strip_leading Built-In (since 1.11.0)

This built-in removes leading Unicode whitespaces[4] from the text. The examples use as a replacement for the whitespace.

Input Output

${'␣␣␣The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog␣␣'?strip_leading}

The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog␣␣

==== strip_trailing Built-In (1.11.0)

This built-in removes trailing Unicode whitespaces[4] from the text. The examples use as a replacement for the whitespace.

Input Output

${'␣␣␣The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog␣␣'?strip_trailing}

␣␣␣The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog

==== strip_to_null Built-In (since 1.11.0)

This built-in removes leading and trailing Unicode whitespaces[4] from the text. The examples use as a replacement for the whitespace. If the result is an empty string, NULL is returned. If the text is NULL, then NULL is returned instead of throwing an exception.[5]

Input Output Type

${'␣␣␣The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog␣␣'?trim_to_null}

The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog

string

${'␣␣␣␣␣'?trim_to_null}

NULL

NULL

${null?trim_to_null}

NULL

NULL

==== trim Built-In

This built-in removes leading and trailing whitespaces from the text. The examples use as a replacement for the whitespace.

Input Output

${'␣␣␣The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog␣␣'?trim}

The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog

==== trim_to_null Built-In (since 1.7.1)

This built-in removes leading and trailing whitespaces from the text. The examples use as a replacement for the whitespace. If the result is an empty string, NULL is returned. If the text is NULL, then NULL is returned instead of throwing an exception.[5]

Input Output Type

${'␣␣␣The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog␣␣'?trim_to_null}

The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog

string

${'␣␣␣␣␣'?trim_to_null}

NULL

NULL

${null?trim_to_null}

NULL

NULL

==== empty_to_null Built-In (since 1.7.1)

This built-in returns NULL if the text is empty. If the text is NULL, then NULL is returned instead of throwing an exception.[5]

Input Output Type

${'The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog'?empty_to_null}

The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog

string

${''?empty_to_null}

NULL

NULL

${null?empty_to_empty}

NULL

NULL

==== blank_to_null Built-In (since 1.7.1)

This built-in returns NULL if the text is blank. The examples use as a replacement for the whitespace. If the text is NULL, then NULL is returned instead of throwing an exception.[5]

Input Output Type

${'The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog'?blank_to_null}

The␣quick␣brown␣Fox␣jumps␣over␣the␣lazy␣Dog

string

${'␣␣␣␣'?blank_to_null}

NULL

NULL

${null?blank_to_null}

NULL

NULL

==== contains Built-In

This built-in return true if the text contains the specified parameter, otherwise false.

Interpolation Output

'The quick brown Fox jumps over the lazy Dog'?contains('Fox')

true

${'The quick brown Fox jumps over the lazy Dog'?contains('Cat')}

false

==== startsWith Built-In

This built-in return true if the text starts with the specified parameter, otherwise false.

Interpolation Output

${'The quick brown Fox jumps over the lazy Dog'?startsWith('Dog')}

true

${'The quick brown Fox jumps over the lazy Dog'?starts_with('Cat')}

false

==== endsWith Built-In

This built-in return true if the text ends with the specified parameter, otherwise false.

Interpolation Output

${'The quick brown Fox jumps over the lazy Dog'?endsWith('Fox')}

true

${'The quick brown Fox jumps over the lazy Dog'?ends_with('Cat')}

false

==== toBoolean Built-In

This built-in return true if the text is true and false if it is false.

Interpolation Output

${'true'?toBoolean}

true

${'false'?to_boolean}

false

${'gonzo'?toBoolean}

This is an error.

==== length Built-In

This built-in return the length of the text.

Interpolation Output

${'Supercalifragilisticexpialidocious'?length}

34

${''?length}

0

==== is_empty Built-In (since 1.11.0)

This built-in return true for an empty text and false otherwise.

Interpolation Output

${''?is_empty}

true

${'text'?is_empty}

false

==== esc, escape Built-In

This built-in convert the text into markup with the specified output format. It cannot be processed further because there are no built-ins for markup.

Interpolation Output

${'<>'?esc('HTML')}

&lt;&gt;

${'<>'?esc('plainText')}

<>

==== noEsc, no_esc, no_escape Built-In

This built-in convert the text into markup without an output format. This can be useful if the current value should not escape with the current output format. It cannot be processed further because there are no built-ins for markup.

Interpolation Output

${'<>'?no_esc}

<>

==== locale Built-In

This built-in converts a string value into a locale value.

Interpolation Output Type

${'de_DE'?locale}

de_DE

locale

${'de_DE'?locale?language}

de

string

${'de'?locale}

de

locale

==== version Built-In

This built-in converts a string value into a version value.

Interpolation Output Type

${'1.0.2'?version}

1.0.2

version

${'1.0.2'?version?patch}

2

number

==== i18n Built-In (experimental, since 1.6.7)

This built-in returns a string from a resource bundle based on the key in the value.

the resource bundle name must be added to the template via the Template#addResourceBundle method or used as the parameter of the built-in.
Interpolation Output Resource Bundle

${'key'?i18n}

value

key=value

${'key'?i18n('name')}

value

key=value

${missingKey?i18n!'-'}

-

==== left_pad Built-In (since 1.8.1)

This built-in returns a string with the minimum length that was specified as the first parameter. If the original string is already this long or longer, the return value is equal to the original string.

The string is padded on the left with whitespaces or a character from the optional padding parameter. If the padding parameter is longer than one character, then the padding is applied cyclically.

The examples use as a replacement for the whitespace.

Interpolation Output

${'apple'?left_pad(8)}

␣␣␣apple

${'orange'?left_pad(8)}

␣␣orange

${'pineapple'?left_pad(8)}

pineapple

${'apple'?left_pad(8, '●')}

●●●apple

${'apple'?left_pad(8, '●○')}

●○●apple

${''?left_pad(40, '▲▼')}

▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼

${''?left_pad(40, '•●⬤●')}

•●⬤●•●⬤●•●⬤●•●⬤●•●⬤●•●⬤●•●⬤●•●⬤●•●⬤

==== right_pad Built-In (since 1.8.1)

This built-in returns a string with the minimum length that was specified as the first parameter. If the original string is already this long or longer, the return value is equal to the original string.

The string is padded on the right with whitespaces or a character from the optional padding parameter. If the padding parameter is longer than one character, then the padding is applied cyclically.

The examples use as a replacement for the whitespace.

Interpolation Output

${'apple'?right_pad(8)}

apple␣␣␣

${'orange'?right_pad(8)}

orange␣␣

${'pineapple'?right_pad(8)}

pineapple

${'apple'?right_pad(8, '●')}

apple●●●

${'apple'?right_pad(8, '●○')}

apple●○●

${''?right_pad(40, '▲▼')}

▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼▲▼

${''?right_pad(40, '•●⬤●')}

•●⬤●•●⬤●•●⬤●•●⬤●•●⬤●•●⬤●•●⬤●•●⬤●•●⬤

==== center_pad Built-In (since 1.9.0)

This built-in returns a string with the minimum length that was specified as the first parameter. If the original string is already this long or longer, the return value is equal to the original string.

The string is padded on both sides with whitespaces or a character from the optional padding parameter. If the padding parameter is longer than one character, then the padding is applied cyclically.

The examples use as a replacement for the whitespace.

Interpolation Output

${'apple'?center_pad(8)}

␣␣apple␣␣

${'orange'?center_pad(8)}

␣orange␣

${'pineapple'?center_pad(8)}

pineapple

${'apple'?center_pad(8, '●')}

●●apple●

${'apple'?center_pad(8, '●○')}

●○apple○

==== mask Built-In (since 1.9.0)

This built-in returns a masked string with the length of the original string. Every none space character is replaced by characters from the mask pattern.

If the first parameter is a string, then it is the mask pattern. Otherwise, it is the asterisk character. If the mask pattern is longer than one character, then the mask pattern is applied cyclically.

A first number parameter is the number of unmasked characters at the end of the masked string.

Interpolation Output

${'secret'?mask}

******

${'+49 176 04069042'?mask}

*** *** ********

${'+49 176 04069042'?mask('●')}

●●● ●●● ●●●●●●●●

${'+49 176 04069042'?mask(2)}

*** *** ******42

${'+49 176 04069042'?mask('●○', 2)}

●○● ●○● ●○●○●○42

==== mask_full Built-In (since 1.9.0)

This built-in returns a masked string with the length of the original string. Every character is replaced by characters from the mask pattern.

If the first parameter is a string, then it is the mask pattern. Otherwise, it is the asterisk character. If the mask pattern is longer than one character, then the mask pattern is applied cyclically.

A first number parameter is the number of unmasked characters at the end of the masked string.

Interpolation Output

${'secret'?mask}

******

${'+49 176 04069042'?mask_full}

****************

${'+49 176 04069042'?mask_full('✱')}

✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱

${'+49 176 04069042'?mask_full(2)}

**************42

${'+49 176 04069042'?mask_full('●○', 2)}

●○●○●○●○●○●○●○42

=== Boolean Built-Ins

==== c (computer language) Built-In

This built-in converts a boolean to the string literals true or false.

==== h (human language) Built-In (since 1.4.6)

This built-in converts a boolean to the localized string literals true or false.

Localizations is only supported for de, en and fr. If you need another one or want to change a supported one put a resource file freshmarker_xx.properties on the classpath. It should contain all localized values of the original ones where xx is the selected language.

default values in freshmarker.properties
boolean.true=true
boolean.false=false
number.1=one
number.2=two
number.3=three
number.4=four
number.5=five
number.6=six
number.7=seven
number.8=eight
number.9=nine
date.-2=the day before yesterday
date.-1=yesterday
date.0=today
date.1=tomorrow
date.2=the day after tomorrow
period.day=day
period.days=days
period.month=month
period.months=months
period.year=year
period.years=years
Interpolation Output

<#setting locale="fr-FR">${true?h}

vrai

<#setting locale="de">${false?h}

falsch

==== then Built-In

This built-in returns its first parameter if the value is true or the second parameter if the value is false.

Interpolation Output

${true?then(23, 42)}

23

${false?then(23, 42)}

42

=== Number Built-Ins

==== c (computer language) Built-In

This built-in converts a number to the string using the toString() method of the corresponding base types BigDecimal, BigInteger, Double, Float, Long, AtomicLong, Integer, AtomiInteger, Byte or Short.

Without this builtin, the number is formatted with the default number formatter for the current locale.

==== h (human language) Built-In (since 1.4.6)

This built-in formats the numbers between 1 and 9 as localized strings 'one' to 'nine'. Other numbers are returned as numbers and formatted by default.

Localizations is only supported for de, en and fr. If you need another one or want to change a supported one put a resource file freshmarker_xx.properties on the classpath. It should contain all localized values of the original ones where xx is the selected language.

default values in freshmarker.properties
boolean.true=true
boolean.false=false
number.1=one
number.2=two
number.3=three
number.4=four
number.5=five
number.6=six
number.7=seven
number.8=eight
number.9=nine
date.-2=the day before yesterday
date.-1=yesterday
date.0=today
date.1=tomorrow
date.2=the day after tomorrow
period.day=day
period.days=days
period.month=month
period.months=months
period.year=year
period.years=years
Interpolation Locale Output

${0?h}

Locale.GERMANY

0

${1?h}

Locale.GERMANY

eins

${9?h}

Locale.US

nine

${10?h}

Locale.US

10

==== format Built-In

This built-in format the number by the given format parameter and the current locale. See java.util.Formatter for details.

Interpolation Locale Output

${3.14159?format("%.2f")}

Locale.GERMANY

3.14

${3.14159?format("%.4f")}

Locale.US

3.1416

==== abs Built-In

This built-in return the absolute value of the number.

Interpolation Output

${-3.14159?abs}

3.142

${3.14159?abs}

3.142

${-42?abs}

42

==== sign Built-In

This built-in return the sign of the number as an Integer.

Interpolation Output

${-3.14159?sign}

-1

${3.14159?sign}

1

${0?sign}

0

==== min Built-In (since 1.6.0)

This built-in returns the smaller value of the variable and the specified parameter.

Interpolation Output

${3.14159?min(3)}

3

${3.14159?min(4)}

3.14159

==== max Built-In (since 1.6.0)

This built-in returns the bigger value of the variable and the specified parameter.

Interpolation Output

${3.14159?max(3)}

3.14159

${3.14159?max(4)}

4

==== Cast Built-Ins (since 1.0.0)

These built-ins cast the number to a TemplateNumber instance in the specified type range.

Interpolation Output Type

${42?byte}

42

Byte

${42?short}

42

Short

${42?int}

42

Integer

${42?long}

42

Long

${42.0?float}

42.0

Float

${42.0?double}

42.0

Double

${42.0?big_integer}

42

BigInteger

${42.0?big_decimal}

42.0

BigDecimal

==== roman Built-In (since 1.3.5)

This built-in converts numbers between 1 and 3999 to roman numerals.

Interpolation Output

${1?roman}

I

${4?roman}

IV

${2012?roman}

MMXII

==== utf_roman Built-In (since 1.3.5)

This built-in converts numbers between 1 and 3999 to roman numerals. This built-in uses the Unicode Characters \u2160, \u2164, \u2169 and \u216C to \u216F.

Interpolation Output

${1?utf_roman}

${4?utf_roman}

ⅠⅤ

${2012?utf_roman}

ⅯⅯⅩⅠⅠ

==== clock_roman Built-In (since 1.3.5)

This built-in converts numbers between 1 and 12 to roman numerals. This built-in uses the Unicode Characters from \u2160 to \u216B.

Interpolation Output

${1?utf_roman}

${4?utf_roman}

${9?utf_roman}

==== clamp Built-In (since 1.10.0)

Clamps the value to fit between min and max. If the value is less than min, then min is returned. If the value is greater than max, then max is returned. Otherwise, the original value is returned.

Interpolation Output

${10?clamp(20, 30)}

20

${25?clamp(20, 30)}

25

${40?clamp(20, 30)}

30

==== unicode Built-In (since 1.10.0)

Convert value into the corresponding Unicode character. If the number is not an integer, it is converted appropriately. With an additional parameter, this is added to the value as an offset.

Interpolation Output

${48?unicode}

0

${9312?unicode}

${40?unicode(8)}

0

${9300?unicode(12)}

=== Character Built-Ins (since 1.10.0)

==== c (computer language) Built-In

This built-in converts a character to a string .

==== is_whitespace Built-In

This built-in returns true if the character is a whitespace and false otherwise (see java.lang.Character#isWitespace).

==== is_digit Built-In

This built-in returns true if the character is a digit and false otherwise. See java.lang.Character#isDigit).

==== is_letter Built-In

This built-in returns true if the character is a letter and false otherwise (see java.lang.Character#isLetter).

==== is_upper_case Built-In

This built-in returns true if the character is an uppercase character and false otherwise (see java.lang.Character#isUpperCase).

Interpolation Output Type

${'A'[0]?is_upper_case}

false

boolean

${'a'[0]?is_upper_case}

true

boolean

==== is_lower_case Built-In

This built-in returns true if the character is a lowercase character and false otherwise (see java.lang.Character#isLowerCase).

Interpolation Output Type

${'A'[0]?is_lower_case}

false

boolean

${'a'[0]?is_lower_case}

true

boolean

==== is_alphabetic Built-In

This built-in returns true if the character is an alphabetic character and false otherwise (see java.lang.Character#isAlphabetic).

==== is_letter Built-In

This built-in returns true if the character is a letter and false otherwise (see java.lang.Character#isLetter).

==== is_emoji Built-In (experimental)

This built-in returns true if the character is an emoji and false otherwise (see java.lang.Character#isLowerCase).

==== upper_case Built-In

This built-in converts the character to uppercase (see java.lang.Character#toUpperCase).

Interpolation Output Type

${'A'[0]?upper_case}

A

character

${'a'[0]?upper_case}

A

character

==== lower_case Built-In This built-in converts the character to lowercase (see java.lang.Character#toLowerCase).

Interpolation Output Type

${'A'[0]?lower_case}

a

character

${'a'[0]?lower_case}

a

character

==== unicode_block Built-In (experimental) This built-in returns the name of the containing unicode block.

Interpolation Output Type

${'€❶'[0]?unicode_block}

CURRENCY_SYMBOLS

string

${'€❶'[1]?unicode_block}

DINGBATS

string

=== Date-Time Built-Ins

The Built-Ins in the examples operate on the java.time.Instant, java.time.ZonedDateTime, java.time.OffsetDateTime,java.time.LocalDateTime respectively java.util.Date value 1968-08-24 12:34:56.

==== date Built-In

This built-in returns the date part of the value.

Interpolation Output Type

${temporal?date}

1968-08-24

date

==== time Built-In

This built-in returns the time part of the value.

Interpolation Output Type

${temporal?time}

12:34:56

time

==== c (computer language) Built-In

This built-in returns a timestamp string of the value.

Interpolation Output Type

${temporal?c}

1968-08-24T12:34:56

string

==== string Built-In

This built-in returns a formatted string of the value. [6]

Interpolation Output Type

${temporal?string('dd. MMMM yyyy hh:mm')}

24. August 1968

string

==== at_zone Built-In

This built-in returns a temporal at the given time zone of the value. [6]

Interpolation Output Type

${temporal?at_zone('Europe/Berlin')}

1968-08-24 12:30:45 Europe/Berlin

date time (TemplateZonedDateTime)

==== zone Built-In

This built-in returns a string for the current time zone of the value. It is only available for the java.time.TemplateZonedDateTime type.

Interpolation Output Type

${temporal?zone}

Europe/Berlin

string

==== year Built-In (since 1.7.0)

This built-in returns the year of the value.[7]

Interpolation Output Type

${temporal?year}

1968

year

==== month Built-In (since 1.7.0)

This built-in returns the month of the value.[7]

Interpolation Output Type

${temporal?months}

AUGUST

month

==== day Built-In (since 1.7.0)

This built-in returns the day of month of the value as a number.[7]

Interpolation Output Type

${temporal?day}

24

number

==== easter Built-In (since 1.8.0)

This built-in returns the date of Easter sunday in the year of the value as a date.[6]

Interpolation Output Type

${temporal?day}

1968-04-14

date

==== supports Built-In (since 1.8.2)

This built-in returns true for the supported temporal units, false otherwise. The currently available temporal units YEARS, MONTHS, DAYS, HOURS, MINUTES and SECONDS are all supported.

For Instant values only DAYS, HOURS, MINUTES and SECONDS return true.
Interpolation Output Type

${temporal?supports('YEARS'}

true

boolean

${temporal?supports('MONTHS'}

true

boolean

${temporal?supports('DAYS'}

true

boolean

=== Date Built-Ins

The Built-Ins in the examples operate on the java.time.LocalDate respectively java.sql.Date value 1968-08-24.

==== date Built-In

This built-in returns the date part of the value.

This built-in seems pointless at first because the result corresponds to the original value. However, it is often not certain what type the variable actually has. With a datetime value, the time would otherwise also be used without ?date.
Interpolation Output Type

${temporal?c}

1968-08-24

date

==== c (computer language) Built-In

This built-in returns a date string of the value.

Interpolation Output Type

${temporal?c}

1968-08-24

string

==== h (human language) Built-In (since 1.4.6)

This built-in returns a date string of the value. If the date is a maximum of two days before or after the reference date, a localized variant of the the day before yesterday, yesterday, today, tomorrow or the day after tomorrow is used. The reference date is the parameter of the built-in. If the parameter is missing, LocalDate.now() is used. [6]

Localizations is only supported for de, en and fr. If you need another one or want to change a supported one put a resource file freshmarker_xx.properties on the classpath. It should contain all localized values of the original ones where xx is the selected language.

default values in freshmarker.properties
boolean.true=true
boolean.false=false
number.1=one
number.2=two
number.3=three
number.4=four
number.5=five
number.6=six
number.7=seven
number.8=eight
number.9=nine
date.-2=the day before yesterday
date.-1=yesterday
date.0=today
date.1=tomorrow
date.2=the day after tomorrow
period.day=day
period.days=days
period.month=month
period.months=months
period.year=year
period.years=years
Interpolation Output Type

${.now?h}

today

string

${temporal?h(.now)}

1968-08-24

date

${dayBeforeTemporal?h(temporal)}

yesterday

string

==== string Built-In

This built-in returns a formatted string of the value. [6]

Interpolation Output Type

${temporal?string('dd. MMMM yyyy')}

24. August 1968

string

==== since Built-In (since 1.6.1)

This built-in returns a duration between the value and the parameter. If the parameter is missing, java.time.LocalDate.now() is used. [6]

Interpolation Output Type

${tomorrow?since}

P1D

duration

${tomorrow?since(yesterday)}

P2D

duration

==== until Built-In (since 1.6.1)

This built-in returns a duration between the value and the parameter. If the parameter is missing, java.time.LocalDate.now() is used. [6]

Interpolation Output Type

${yesterday?until}

P1D

duration

${yesterday?until(tomorrow)}

P2D

duration

==== year Built-In (since 1.7.0)

This built-in returns the year of the value.[6]

Interpolation Output Type

${temporal?year}

1968

year

==== month Built-In (since 1.7.0)

This built-in returns the month of the value.[6]

Interpolation Output Type

${temporal?months}

AUGUST

month

==== day Built-In (since 1.7.0)

This built-in returns the day of month of the value as a number.[6]

Interpolation Output Type

${temporal?day}

24

number

==== easter Built-In (since 1.8.0)

This built-in returns the date of Easter sunday in the year of the value as a date.[6]

Interpolation Output Type

${temporal?day}

1968-04-14

date

==== supports Built-In (since 1.8.2)

This built-in returns true for the supported temporal units, false otherwise. From the currently available temporal units only YEARS, MONTHS and DAYS are supported.

Interpolation Output Type

${temporal?supports('YEARS'}

true

boolean

${temporal?supports('MONTHS'}

true

boolean

${temporal?supports('DAYS'}

true

boolean

=== Time Built-Ins

The Built-Ins in the examples operate on the java.time.LocalTime respectively java.sql.Time value 12:34:56.

==== time Built-In

This built-in returns the time part of the value.

This built-in seems pointless at first because the result corresponds to the original value. However, it is often not certain what type the variable actually has. With a datetime value, the date would otherwise also be used without ?time.
Interpolation Output Type

${temporal?time}

12:34:56

time

==== c (computer language) Built-In

This built-in returns a time string of the value.

Interpolation Output Type

${temporal?c}

12:34:56

string

==== string Built-In

This built-in returns a formatted string of the value. [6]

Interpolation Output Type

${temporal?string('hh:mm')}

24. August 1968

string

==== supports Built-In (since 1.8.2)

This built-in returns true for the supported temporal units, false otherwise. From the currently available temporal units only HOURS, MINUTES and SECONDS are supported.

This built-in exists for time values so that all temporal types can be checked for temporal units.
Interpolation Output Type

${temporal?supports('YEARS'}

false

boolean

${temporal?supports('MONTHS'}

false

boolean

${temporal?supports('DAYS'}

false

boolean

=== Year Built-Ins

The Built-Ins in the examples operate on the value 2025.

==== c (computer language) Built-In (since 1.7.0)

This built-in returns a year string of the value.

Interpolation Output Type

${temporal?c}

2025

string

==== year Built-In (since 1.7.0)

This built-in returns the year of the value.

Interpolation Output Type

${temporal?year}

2025

year

==== is_leap Built-In (since 1.7.0)

This built-in returns true if the year is a leap year.

Interpolation Output Type

${temporal?is_leap}

false

boolean

==== easter Built-In (since 1.8.0)

This built-in returns the date of Easter sunday in the year of the value as a date.

Interpolation Output Type

${temporal?day}

2025-04-20

date

==== supports Built-In (since 1.8.2)

This built-in returns true for the supported temporal units, false otherwise. From the currently available temporal units only YEARS is supported.

Interpolation Output Type

${temporal?supports('YEARS'}

true

boolean

${temporal?supports('MONTHS'}

false

boolean

${temporal?supports('DAYS'}

false

boolean

=== YearMonth Built-Ins

The Built-Ins in the examples operate on the value 2025-08.

==== c (computer language) Built-In (since 1.7.0)

This built-in returns a year string of the value.

Interpolation Output Type

${temporal?c}

2025-08

string

==== year Built-In (since 1.7.0)

This built-in returns the year of the value.

Interpolation Output Type

${temporal?year}

2025

year

==== month Built-In (since 1.7.0)

This built-in returns the month of the value.

Interpolation Output Type

${temporal?months}

AUGUST

month

==== is_leap Built-In (since 1.7.0)

This built-in returns true if the year is a leap year.

Interpolation Output Type

${temporal?is_leap}

false

boolean

==== easter Built-In (since 1.8.0)

This built-in returns the date of Easter sunday in the year of the value as a date.

Interpolation Output Type

${temporal?day}

2025-04-20

date

==== supports Built-In (since 1.8.2)

This built-in returns true for the supported temporal units, false otherwise. From the currently available temporal units only YEARS and MONTHS are supported.

Interpolation Output Type

${temporal?supports('YEARS'}

true

boolean

${temporal?supports('MONTHS'}

true

boolean

${temporal?supports('DAYS'}

false

boolean

=== MonthDay Built-Ins

The Built-Ins in the examples operate on the value 08-24.

==== c (computer language) Built-In (since 1.7.0)

This built-in returns a year string of the value.

Interpolation Output Type

${temporal?c}

08-24

string

==== month Built-In (since 1.7.0)

This built-in returns the month of the value.

Interpolation Output Type

${temporal?months}

AUGUST

month

==== day Built-In (since 1.7.0)

This built-in returns the day of month of the value as a number

Interpolation Output Type

${temporal?day}

24

number

==== supports Built-In (since 1.8.2)

This built-in returns true for the supported temporal unit, false otherwise. For this type only MONTHS and DAYS are supported.

Interpolation Output Type

${temporal?supports('YEAR'}

false

boolean

${temporal?supports('MONTH'}

true

boolean

${temporal?supports('DAYS'}

false

true

=== Sequence Built-Ins

The Built-Ins in the examples operate on a sequence with the values 2, 3, 4, 5 and 6.

==== first Built-In

This built-in returns the first element of a sequence.

Template Output

${list?first}

2

==== last Built-In

This built-in returns the last element of a sequence.

Template Output

${list?last}

6

==== reverse Built-In

This built-in returns a reversed sequence of a sequence.

Template Output

${list?reverse?first}

6

==== size Built-In

This built-in returns the size of a sequence as a number.

Template Output

${list?size}

5

==== join Built-In (since 1.5.1)

This built-in returns the elements of a sequence as a string. The default limiter is ", " but can be changed with the first optional parameter of the built-in. The second optional parameter is an alternative parameter for the last element in the sequence.

Template Output

${list?join}

2, 3, 4, 5, 6

${list?join(':')}

2:3:4:5:6

${list?join(', ', ' and ')}

2, 3, 4, 5 and 6

==== size Built-In

This built-in return the size of the text.

Interpolation Output

${[]?size}

0

${list?size}

5

==== is_empty Built-In (since 1.11.0)

This built-in return true for an empty sequence and false otherwise.

Interpolation Output

${[]?is_empty}

true

${list?is_empty}

false

=== Range Built-Ins

==== lower Built-In (since 1.6.4)

This built-in returns the lower bound of a range.

Template Output

${(2..6)?lower}

2

${(2..?lower}

2

==== upper Built-In (since 1.6.4)

This built-in returns the upper bound of a limited range.

Template Output

${(2..6)?last}

6

==== reverse Built-In (since 1.6.4)

This built-in returns a reversed limited range.

Template Output

${(2..6)?reverse?join}

6, 5, 4, 3, 2

==== size Built-In (since 1.6.4)

This built-in returns the size of a limited range as a number.

Template Output

${(2..6)?size}

5

==== join Built-In (since 1.6.4)

This built-in returns the elements of a limited range as a string. The default limiter is ", " but can be changed with the first optional parameter of the built-in. The second optional parameter is an alternative parameter for the last element in the sequence.

Template Output

${(2..6)?join}

2, 3, 4, 5, 6

${(2..6)?join(':')}

2:3:4:5:6

${(2..6)?join(', ', ' and ')}

2, 3, 4, 5 and 6

==== size Built-In

This built-in return the size of a limited range.

Interpolation Output

${(0..<0)?size}

0

${(0..6)?length}

5

==== is_empty Built-In (since 1.11.0)

This built-in return true for an empty range and false otherwise.

Interpolation Output

${(0..<0)?is_empty}

true

${(0..1)?is_empty}

false

==== is_limited Built-In (since 2.0.0)

This built-in return true for a limited range and false otherwise.

Interpolation Output

${(1..42)?is_limited}

true

${(0..)?is_limited}

false

==== is_unlimited Built-In (since 2.0.0)

This built-in return true for an unlimited range and false otherwise.

Interpolation Output

${(1..42)?is_limited}

true

${(0..)?is_limited}

false

=== Looper Built-Ins

==== index Built-In

This built-in returns the index (starting with zero) of the current loop value as a number value.

Template Output

<#list (1..10) as s with l>${l?index}, </#list>

0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

==== counter Built-In

This built-in returns the counter (starting with one) of the current loop value as a number value.

Template Output

<#list (1..10) as s with l>${l?counter}, </#list>

1, 2, 3, 4, 5, 6, 7, 8, 9, 10,

==== roman Built-In (since 1.3.5)

This built-in returns the counter (starting with one) of the current loop value as a roman number value.

Template Output

<#list (1..10) as s with l>${l?roman}, </#list>

I, II, III, IV, V, VI, VII, VIII, IX, X,

==== utf_roman Built-In (since 1.3.5)

This built-in returns the counter (starting with one) of the current loop value as a roman number value. This built-in uses the Unicode Characters \u2160, \u2164, \u2169 and \u216C to \u216F.

Template Output

<#list (1..10) as s with l>${l?utf_roman}, </#list>

Ⅰ, ⅠⅠ, ⅠⅠⅠ, ⅠⅤ, Ⅴ, ⅤⅠ, ⅤⅠⅠ, ⅤⅠⅠⅠ, ⅠⅩ, Ⅹ,

==== clock_roman Built-In (since 1.3.5)

This built-in returns the counter (starting with one up to twelve) of the current loop value as a roman number value. This built-in uses the Unicode Characters from \u2160 to \u216B.

Template

Output

<#list (1..12) as s with l>${l?clock_roman}, </#list>

Ⅰ, Ⅱ, Ⅲ, Ⅳ, Ⅴ, Ⅵ, Ⅶ, Ⅷ, Ⅸ, Ⅹ, Ⅺ, Ⅻ,

==== is_first Built-In

This built-in returns the boolean value true, if the current loop value is the first one and false otherwise.

Template Output

<#list (1..10) as s with l>${l?is_first}, </#list>

true, false, false, false, false, false, false, false, false, false,

This built-in useful, if the first item in a sequence should look different.

Table 4. Loop with special first element
Template Output
<#list (1..10) as s with l>
<#if l?is_first>Sequence: </#if>${s}
</#list>

Sequence: 1 2 3 4 5 6 7 8 9 10

==== is_last Built-In

This built-in returns the boolean value true, if the current loop value is the last one and false otherwise.

Template Output

<#list (1..10) as s with l>${l?is_last}, </#list>

false, false, false, false, false, false, false, false, false, true,

This built-in useful, if the last item in a sequence should look different.

Table 5. Loop with special last element
Template Output
<#list (1..10) as s with l>
${s} <##if l?is_last>QED</#if>
</#list>

1 2 3 4 5 6 7 8 9 10 QED

==== item_parity Built-In

This built-in returns the string value even, if the current loop counter is even and odd otherwise. It is useful for CSS class names.

Template Output

<#list (1..10) as s with l>${l?item_parity}, </#list>

odd, even, odd, even, odd, even, odd, even, odd, even,

==== item_parity_cap Built-In

This built-in returns the string value Even, if the current loop counter is even and Odd otherwise. It is useful for CSS class names.

Template Output

<#list (1..10) as s with l>${l?item_parity_cap}, </#list>

Odd, Even, Odd, Even, Odd, Even, Odd, Even, Odd, Even,

==== item_cycle Built-In

This built-in cycles throw the parameters for the current loop counter. It is useful for CSS class names.

Template Output

<#list (1..10) as s with l>${l?item_cycle('one', 'two', 'three'}, </#list>

one, two, three, one, two, three, one, two, three, one,

==== has_next Built-In

This built-in returns false, if the current loop value is the last one and true otherwise.

Template Output

<#list (1..10) as s with l>${l?has_next}, </#list>

true, true, true, true, true, true, true, true, true, false,

This built-in useful, if the last item in a sequence should look different.

Table 6. Loop with special last element
Template Output
<#list (1..10) as s with l>
${s}<#if l?has_next>, </#if>
</#list>

1, 2, 3, 4, 5, 6, 7, 8, 9, 10

=== Enum Built-Ins

The Built-Ins in the examples operate on the enum value java.nio.file.StandardCopyOption.ATOMIC_MOVE.

==== ordinal Built-In

This built-in returns the ordinal of the value.

Interpolation Output Type

${enum?ordinal}

2

number

==== c (computer language) Built-In

This built-in returns the name of the value.

Interpolation Output Type

${enum?c}

ATOMIC_MOVE

number

=== File and Path Built-Ins

Since FreshMarker 1.3.2 the File and Paths BuiltIns are included via an additional artefact.

<dependency>
  <groupId>de.schegge</groupId>
  <artifactId>freshmarker-file</artifactId>
  <version>1.6.1</version>
</dependency>
if these built-ins are used carelessly, third parties may receive information about files on the server.

The Built-Ins in the examples operate on the java.io.File respectively java.nio.file.Path value /src/main/asciidoc/manual.adoc.

==== exists Built-In

This built-in returns true, if the file/path in the value exists.

Interpolation Output Type

${file?exists}

true

boolean

==== is_directory Built-In

This built-in returns true, if the file/path in the value is a directory.

Interpolation Output Type

${file?is_directory}

false

boolean

==== is_file Built-In

This built-in returns true, if the file/path in the value is a file.

Interpolation Output Type

${file?is_file}

true

boolean

==== can_execute Built-In

This built-in returns true, if the file/path in the value can be executed.

Interpolation Output Type

${file?can_execute}

false

boolean

==== can_read Built-In

This built-in returns true, if the file/path in the value can be read.

Interpolation Output Type

${file?can_read}

true

boolean

==== can_write Built-In

This built-in returns true, if the file/path in the value can be written.

Interpolation Output Type

${file?can_write}

true

boolean

==== size Built-In

This built-in returns the size of the file/path in the value.

Interpolation Output Type

${file?size}

764

number

==== name Built-In

This built-in returns the name of the file/path in the value.

Interpolation Output Type

${file?name}

manual.adoc

string

==== parent Built-In

This built-in returns the parent file/path of the value.

Interpolation Output Type

${file?parent}

/src/main/asciidoc

file/path

=== Locale Built-Ins

The Built-Ins in the examples operate on the locale value Locale.GERMANY.

==== lang, language Built-In

This built-in returns the language part of the locale value.

Interpolation Output Type

${.locale?lang}

de

string

${.locale?language}

de

string

==== language_name Built-In (since 1.7.5)

This built-in returns the language name of the locale value.

Interpolation Output Type

${.locale?language_name}

Deutsch

string

==== country Built-In

This built-in returns the country part of the locale value.

Interpolation Output Type

${.locale?country}

DE

string

==== country_name Built-In (since 1.7.5)

This built-in returns the country name of the locale value.

Interpolation Output Type

${.locale?country_name}

Deutschland

string

=== Version Built-Ins

The Built-Ins in the examples operate on the version value 1.0.2.

==== major Built-In

This built-in returns the major part of the version value.

Interpolation Output Type

${.version?major}

1

number

==== minor Built-In

This built-in returns the minor part of the version value.

Interpolation Output Type

${.version?minor}

0

number

==== patch Built-In

This built-in returns the patch part of the version value.

Interpolation Output Type

${.version?patch}

2

number

==== is_before Built-In

This built-in return true if the version is before the specified parameter, otherwise false. The type of the parameter is string or version.

Interpolation Output Type

${.version?is_before('2.0.0')}

yes

boolean

${.version?is_before(.version)}

no

boolean

==== is_equal Built-In

This built-in return true if the version is equal the specified parameter, otherwise false. The type of the parameter is string or version.

Interpolation Output Type

${.version?is_equal('1.0.2')}

yes

boolean

${.version?is_equal('1.2.3')}

no

boolean

==== is_after Built-In

This built-in return true if the version is after the specified parameter, otherwise false. The type of the parameter is string or version.

Interpolation Output Type

${.version?is_after('1.0.0')}

yes

boolean

${.version?is_after(.version)}

no

boolean

=== Random Built-Ins

The Random built-ins are included via an additional artefact.

<dependency>
  <groupId>de.schegge</groupId>
  <artifactId>1.5.1</artifactId>
  <version>1.0.1</version>
</dependency>

The examples uses the .random built-in variable containing a SecureRandom value and variable abraxas containing a Random value.

==== boolean Built-In

This built-in returns a random boolean value.

Interpolation Output Type

${.random?boolean}

true

boolean

${abraxas?boolean}

false

boolean

==== int Built-In

This built-in returns a random int value. If a parameter is specified, the built-in returns a number between 0 and the parameter.

Interpolation Output Type

${.random?int}

42

int

${abraxas?int(23)}

5

int

==== double Built-In (since 1.2.0)

This built-in returns a random double value. If a parameter is specified, the built-in returns a number between 0 and the parameter.

Interpolation Output Type

${.random?double}

-34.56

double

${abraxas?double(1)}

0.5

double

==== item Built-In

This built-in returns a random item from a sequence.

Interpolation Output

${.random?item([2, 3, 4, 5, 6])}

6

==== uuid Built-In

This built-in returns a random UUID as string.

Interpolation Output Type

${abraxas?uuid)}

696a2615-ef21-40f3-b42b-905a8097927a

string

==== sentence Built-In (since 1.2.0)

This built-in returns a random sentence as string.

Interpolation Output Type

${abraxas?sentence}

Quibusdam distinctio sed vitae minus non error.

string

==== paragraph Built-In (since 1.2.0)

This built-in returns a random paragraph as string.

Interpolation Output Type

${abraxas?paragraph}

Ex quis ut ut voluptatem autem officia. Occaecati non mollitia ipsum dignissimos eligendi tempore. Dolor tenetur illo in inventore.

string

=== Type checking Built-Ins (since 1.8.1)

Normally, the type of a variable does not need to be checked because the data model has been modeled correctly.

Nevertheless, it may occasionally be necessary to check the type of a variable so that it receives special treatment.

This built-ins available on all FreshMarker types (this includes your custom types too).

==== is_null Built-In

This built-in returns the true is the variable is NULL and false otherwise.

It only exists to complete the type checks, normally you use the existence operator.

Interpolation Output Type

${null?is_null}

yes

boolean

${42?is_null}

no

boolean

==== is_string Built-In

This built-in returns the true is the variable contains a string value and false otherwise.

Interpolation Output Type

${'text'?is_string}

yes

boolean

${42?is_string}

no

boolean

==== is_character Built-In

This built-in returns the true is the variable contains a character value and false otherwise.

Interpolation Output Type

${character?is_character}

yes

boolean

${42?is_character}

no

boolean

==== is_boolean Built-In

This built-in returns the true is the variable contains a boolean value and false otherwise.

Interpolation Output Type

${false?is_boolean}

yes

boolean

${42?is_boolean}

no

boolean

==== is_number Built-In

This built-in returns the true is the variable contains a number value and false otherwise.

Interpolation Output Type

${42?is_number}

yes

boolean

${true?is_number}

no

boolean

==== is_sequence Built-In

This built-in returns the true is the variable contains a sequence value and false otherwise.

Interpolation Output Type

${sequence?is_sequence}

yes

boolean

${true?is_sequence}

no

boolean

==== is_hash Built-In

This built-in returns the true is the variable contains a hash value and false otherwise.

Interpolation Output Type

${bean?is_hash}

yes

boolean

${42?is_hash}

no

boolean

==== is_enum Built-In

This built-in returns the true is the variable contains an enum value and false otherwise.

Interpolation Output Type

${enum?is_enum}

yes

boolean

${'text'?is_enum}

no

boolean

==== is_range Built-In

This built-in returns the true is the variable contains a range value and false otherwise.

Interpolation Output Type

${(1..3)?is_range}

yes

boolean

${true?is_range}

no

boolean

==== is_temporal Built-In (since 1.8.2)

This built-in returns the true is the variable contains a temporal value and false otherwise.

A temporal value is one of the types java.util.Date, java.sql.Time, java.sql.Date, java.time.Instant, java.time.ZonedDateTime, java.time.LocalDateTime, java.time.LocalDate, java.time.LocalTime, java.time.Year,java.time.YearMonth and java.time.MonthDay.

Interpolation Output Type

${.now?is_temporal}

yes

boolean

${42?is_temporal}

no

boolean

== Directives

=== If/ElseIf/Else Directive

This directive generates conditional output. The expression in the if and elseif statement is evaluated to a boolean value. If the value is true the corresponding block is evaluated. If the expression cannot evaluate to a boolean value an exception is thrown.

Example with only the if part
<#if text??>
${text}
</#if>

The elseif and else part are optional and can be dismissed.

Example with all parts
<#if number >= 100>
greater equal hundred
<#elseif number > 10>
greater ten
<#else>
less ten
</#if>

=== Switch/Case/Default Directive

This directive generates conditional output. Each case statement handles a possible outcome of the evaluation. With the switch directive, the expressions of the case statements are compared one after the other with the expression in the switch statement. The first case statement that matches is used as the output. If none of the specified case statements match, then the optional default statement is used.

The FreshMarker Switch Directive does not support fall-through, so break statements are unnecessary and therefor not supported.
Example with different types
<#switch color>
<#case 'RED'>#FF0000
<#case 16711680>#FF0000
<#case 'GREEN'>#00FF00
<#case 65280>#00FF00
<#case 'BLUE'>#0000FF
<#case 255>#0000FF
<#default>${color}
</#switch>
Example with variables
<#switch color>
<#case foreground>color: ${color}
<#case background>background-color: ${color}
</#switch>

The case statement expressions do not have to be constants. This means that more complex expressions can also be used. If this is not desired, it can be switched off using the feature SwitchDirectiveFeature.ALLOW_ONLY_CONSTANT_CASES.[8]

In addition, the individual case statement expressions do not have to have the same type. If this is desired, it can be activated via the SwitchDirectiveFeature.ALLOW_ONLY_EQUAL_TYPE_CASES feature. This feature is only taken into account if the feature SwitchDirectiveFeature.ALLOW_ONLY_CONSTANT_CASES is also activated.[8]

=== Switch/On/Default Directive (since 1.7.1)

This directive generates conditional output. Each on statement handles a possible outcome of the evaluation. With the on directive, the expressions of the on statements are compared one after the other with the expression in the sitch statement. The first on statement that matches is used as the output. If none of the specified on statements match, then the optional default statement is used.

The difference between on and case statement is that an on statement can contain several comparison expressions.

<#switch color>
<#on 'RED', 'Crimson', 'DarkRed'>#FF0000
<#on 'GREEN'>#00FF00
<#on 'BLUE', 'Navy'>#0000FF
<#default>${color}
</#switch>

The on statement expressions do not have to be constants. This means that more complex expressions can also be used. If this is not desired, it can be switched off using the feature SwitchDirectiveFeature.ALLOW_ONLY_CONSTANT_ONS.

In addition, the individual on statement expressions do not have to have the same type. If this is desired, it can be activated via the SwitchDirectiveFeature.ALLOW_ONLY_EQUAL_TYPE_ONS feature. This feature is only taken into account if the feature SwitchDirectiveFeature.ALLOW_ONLY_CONSTANT_ONS is also activated.

=== List Directive

The List Directives processes its body for each element of a sequence. The sequence can be either a list, a hash or a range. A List Directive defines the sequence and the name of the item variable in the block. In addition, an optional loop variable containing meta information of the current loop iteration can also be specified.

Although FreeMarker and FreshMaker are similar, the list directive syntax differs. FreeMarker does not know a separate loop variable. In addition, FreshMaker does not support an Else element in the list. This can be replaced by a separate If Directive. FreeMarker's Items Directive is also not supported. there are plenty of ways to get the same result.

==== List Directive with a list

A list is a sequence of values of any model type.

List Directive example with a list
<#list sequence as s>
${s} Marx
</#list>

In the above example, each element from the list is simply output followed by the word Marx. If the list contains Chico, Harpo, Groucho, Gummo and Zeppo then the result is a five-line listing of the Marx brothers.

The next example the interpolation ${s} is preceded by the interpolation ${l?counter}.

List Directive example with a list and a loop variable
<#list sequence as s with l>
${l?counter}. ${s} Marx
</#list>

Because l is the name of the loop variable, the first interpolation evaluates to the counter of the current item in the list.

1. Chico Marx
2. Harpo Marx
3. Groucho Marx
4. Gummo Marx
5. Zeppo Marx

==== List Directive with a range

A range is defined by a lower and a non-mandatory upper numerical limit. Although FreshMarker also masters right-unlimited ranges, List Directives are not allowed to use them.

List Directive example with a range
<#list 1..5 as s>
${s} * ${s} = ${s*s}
</#list>

The current item in the above example is any integer value from 1 to 5 inclusive. The result is a list of square value equations.

1 * 1 = 1
2 * 2 = 4
3 * 3 = 9
4 * 4 = 16
5 * 5 = 25

Because the limits of a range do not have to be constants, a List Directive can also be used to mimic a for loop.

List Directive example with variable range limits
<#list a..b as s>
${s} * ${s} = ${s*s}
</#list>

This example loops through all values between numeric variable a and numeric variable b. For the value 3 in a and 1 in b the following result is generated.

3 * 3 = 9
2 * 2 = 4
1 * 1 = 1

==== List Directive with a hash

A hash is a sequence of key-value pairs. The key is a string and the value is any model type. For a hash the item in the List Directive is a key-value pair.

List Directive example with a hash
<#list map as key, value>
${key}->${value}
</#list>
In addition to the Java Map types, Java Beans and Records are also interpreted as a hash. It is not only possible to iterate over the entries of a Map, but also over the attributes of Java Beans and Records.
List Directive example with a hash and a loop variable
<#list map as key, value with l>
${l?counter}. ${key}->${value}
</#list>

==== List Directive with a hash and sorted keys

Normally the list iterates over the entries without a preferred order. With Java Map types this can be influenced by the use of SequencedMap implementations. However, this is not possible when using a Java Bean.

The keys of a hash can also be sorted in ascending and descending order by adding sorted asc after the key for ascending sorting and sorted desc for descending sorting.

List Directive example with a hash and a sorted key
<#list map as key sorted asc, value>
${key}->${value}
</#list>

==== List Directive with a filter (since 1.6.6)

The list directive runs over all elements of a list. An element can be omitted with an if statement. If the looper variable is also used, this can lead to jumps. There are two solutions to this problem.

The first solution is to include a filtered list in the model and to use this list in the list directive. The second solution is to use a filter expression in the list directive.

The filter expression in the list directive follows directly after the looper part.

List Directive example with filter expression
<#list 1..10 as s filter s % 2 == 0>
${s} * ${s} = ${s*s}
</#list>

==== List Directive with an offset (since 1.7.2)

The start offset in the list directive can be increased using an offset expression. The offset expression in the list directive follows directly after the filter part. If the list ist filtered by a filter expression, the offset is applied to the filtered and not to the original list.

List Directive example with filter expression
<#list 1..10 as s offset 5>
${s} * ${s} = ${s*s}
</#list>

==== List Directive with a limit (since 1.6.6)

The number of elements in the list directive can be reduced using a limit expression. The limit expression in the list directive follows directly after the offset part. If the list ist filtered by a filter expression, the limit is applied to the filtered and not to the original list.

List Directive example with filter expression
<#list 1..10 as s limit 4>
${s} * ${s} = ${s*s}
</#list>

=== Var/Set/Assign Directive

The Var, Set and Assign Directives are used to define local variables in the template. Unlike the model values, new values can be assigned to variables.

The first use of a variable is with the Var Directive. This defines the variables in the current context. The variable can be assigned later new values with the Set and Assign Directive. A variable only exists within its context.

Example Var Directive without value assignment
<#var test/>
<#set test='zwei'/>
<#set test='drei'/>

The Var Directive allows the assignment of an initial value to the variable.

Example Var Directive with value assignment
<#var test='eins'/>
<#set test='zwei'/>
<#set test='drei'/>

Instead of multiple Var Directives a single Var Directive with several variable definitions can be used. The individual definitions can be separated from each other by a space or a comma.

Example Var Directive with multiple variable
<#var test1='eins' test2='zwei'/>
<#var test3, test4/>

A new context is created within the List Directive, Macro Directive and the conditional If and Switch Directives. Therefore, variables that are created in a Macro Directive can no longer be accessed outside.

Since version 1.9.0, it is now possible for all directives to generate a context. See [variable-scope-feature]
Example variable context
<#macro copyright>
  <#var test='test'/>
</#macro>
<@copyright/>
${test!'gonzo'}

In this example the variable test cannot accessed outside of the macro. The result of the interpolation ${test!'gonzo'} is therefore gonzo.

Variables with an identical name cannot created in the same context. Variables in an outer context can be hidden by variables with an identical name.

=== Output-Format Directive

The output-format directive changes the current output format of the template. The new output format remains in place until it is changed by another output-format directive.

<#outputformat 'HTML'>
${text}
<#outputformat 'plainText'>
${text}

=== Setting Directive

The Setting Directive changes some standard values in the template. The settings remains in place until it is changed by another settings directive.

<#setting locale="de-DE">
${42.23}
<#setting locale="en-US">
${42.23}

The following list of settings is supported.

Name

Example

Description

locale

<#setting locale="de-DE">

Set the locale with a language tag.

date_format

<#setting date_format="dd. MMMM yyyy">

Set the format for date values.

time_format

<#setting time_format="hh:mm">

Set the format for time values.

datetime_format

<#setting datetime_format="dd. MMMM yyyy hh:mm">

Set the format for datetime values.

offset_datetime_format

<#setting offset_datetime_format="dd. MMMM yyyy hh:mm">

Set the format for datetime values.

zoned_datetime_format

<#setting zoned_datetime_format="dd. MMMM yyyy hh:mm">

Set the format for datetime values.

zone_id

<#setting zone-id="Europe/Berlin">

Set the zone-id.

=== Macro Directive

The macro directive can be used to create a macro, which can then be used as a user directive in the template.

<#macro copyright from>
/*
 * Copyright © ${from}-${.now?string('yyyy')} Jens Kaiser
 *
 * All Rights resevered.
 */
</#macro from>
<@copyright/>

=== User Directive

The user directive insert the output of Macro directives or UserDirective implementions in the generated content.

<@log level='info' message='test'/>

Some User Directives are available as standard. These are currently the compress and oneliner User Directives.

==== compress User Directive

The compress User Directive reduces whitespaces ( , \n, \r) in the output. Whitespace sequences containing newlines will be replaced by a single newline. Whitespace sequences not containing newlines will be replaced by a single space. Whitespace prefixes and suffixes not containing newlines will be removed.

Template
<@compress>
␣␣⏎
␣␣Hello␣␣␣World␣␣⏎
␣␣␣␣⏎
</@compress>
Output
⏎
Hello World⏎
⏎

==== oneliner User Directive

TBD

=== Import Directive (since 1.0.0)

Macros can be loaded by the import directive.

<#import 'macros.fmt' as m/>

The file name parameter is the name of the import file and will be loaded by default relative to the template file.

<#import 'macros.fmt' as m/>
<@m.copyright/>

=== Include Directive (experimental, since 1.7.0)

Sub-templates can be loaded by the include directive. This feature must be activated with templateBuilder.with(IncludeDirectiveFeature.ENABLED).

<#include 'include.fmt'/>

The file name parameter is the name of the include file and will be loaded by default relative to the template file.

<#import 'macros.fmt' parse=false/>

If the included content does not need to be processed, this can be turned off using the parse attribute. In this case, the content of the include will be included as simple text.

If all includes do not need to be parsed, automatic parsing can be deactivated centrally using the feature IncludeDirectiveFeature.PARSE_BY_DEFAULT. In this case, the parse attribute in the directive is no longer required.

=== Brick Directive (since 1.6.6)

It is clearer if related templates are contained in the same document. The document can be evaluated as a template as a whole or the templates it contains can be evaluated individually.

A part that can be evaluated individually is contained within a brick[9] directive.

Example mail template
<#brick 'salutation'>Hello ${customer.first} ${customer.last},</#brick>
<#brick 'signature'>
Best regards
${employee.first} ${employee.last}
${employee.title}
</#brick>

A single brick directive is evaluated with the Template#processBrick(String, Map) method. The String parameter is the name of the directive.

Example brick rendering
Map<String, Object> model = Map.of("customer", customer, "employee", employee);
Template template = new Configuration().buider().getTemplate("mail", mail);
String salutation = template.processBrick("salutation", model);
String signature = template.processBrick("signature", model);

== Built-In Variables

=== Now Built-In Variable

The .now built-in variable of Type TemplateZoneDateTime provides the current timestamp.

=== Locale Built-In Variable

The .locale built-in variable of Type TemplateLocale provides the current locale.

=== Country Built-In Variable

The .country built-in variable of Type TemplateString provides the current locales country code.

=== Language Built-In Variable

The .laguage built-in variable of Type TemplateString provides the current locales language code.

=== Version Built-In Variable

The .version built-in variable of Type TemplateVersion provides the current FreshMarker version.

=== Random Built-In Variable

The .random built-in variable is included via an additional artefact.

<dependency>
  <groupId>de.schegge</groupId>
  <artifactId>{plugin-random-version}</artifactId>
  <version>1.0.1</version>
</dependency>

The .random built-in variable of Type TemplateRandom can be used to generate random value via several built-ins.

== Expressions

Expressions are an important part of template evaluation. They are evaluated within Conditional Directives and Interpolations.

The expressions are based on Java expressions, but have some differences. The majority of the deviations result from the basic implementation decisions for FreshMarker as a template engine.

As far as practicable, calculations should not take place within the template engine but in the external application. Basically, expressions should be used to make decisions in the Conditional Directives and to influence the output of Interpolations.

For this reason, FreshMarker does not have any bit shift operators, for example. Variables are assigned values within the template via the Var and Set Directives, so FreshMarker does not have an assignment operator or compound operators like Java.

=== Literal Expressions

TBD

=== Inline Sequences

TBD

=== Variables

TBD

=== Method Invocation

TBD

=== Conjunctions

Operator Name Description Example

&

Logical And Operator

evaluates both operands

true & true

Unicode Logical And Operator

evaluates both operands

true ∧ true

&&

Conditional And Operator

evaluates only the first operand, if it evaluates to false

false && true

=== Disjunctions

Operator Name Description Example

|

Logical Or Operator

evaluates both operands

true | true

Unicode Logical Or Operator

evaluates both operands

true ∨ true

||

Conditional Or Operator

evaluates only the first operand, if it evaluates to true

false || true

^

Logical XOR Operator

evaluates both operands

false ^ true

Unicode Logical XOR Operator

evaluates both operands

false ⊻ true

=== Range Operator

TBD

=== Equality Operators

This operator works only on FreshMarkers primitive types. There is no Equality defined on Hashes, Sequences or Ranges.

Operator Name Example

=

Equals Operator

x == 1

!=

Not Equals Operator

x != 2

Unicode Not Equals Operator

x ≠ 3

=== Relational Operators

The greater > and the greater equals >= operator must be surrounded by parenthesis in directive expression to distinguish it from the directive closing symbol >.

Since FreshMarker 1.6.3 model types can overload these operators. Therefore, they are not only available for numeric types, but also for Version, String, Duration, Period and the temporal types. Custom types can also overload all relational operators.

==== Numerical Comparison Operators

Operator Name Example

<

Less Operator

x < 4

<=

Less Equals Operator

x <= 5

Unicode Less Equals Operator

x ≤ 6

>

Greater Operator

x > 7

>=

Greater Equals Operator

x >= 8

Unicode Greater Equals Operator

x ≥ 9

==== String Comparison Operators (since 2.0.0)

Two String values can be compared lexicographic with each other.

Operator Name Example

<

Less Operator

'Jens' < 'Kaiser'

<=

Less Equals Operator

'Jens' <= 'Jens'

Unicode Less Equals Operator

'Fix' ≤ 'Foxy'

>

Greater Operator

'Kaiser' > 'Jens'

>=

Greater Equals Operator

'Jens' >= 'Jens'

Unicode Greater Equals Operator

'Simon' ≥ 'Garfunkel'

==== Temporal Comparison Operators (since 2.0.0)

Two temporal values of the same type can be compared with each other.[6] An exception to this is the type MonthDay, as the year would be required as a context for correct comparison.

Operator Name Example

<

Less Operator

yesterday < today

<=

Less Equals Operator

yesterday <= today

Unicode Less Equals Operator

yesterday ≤ today

>

Greater Operator

tomorrow > today

>=

Greater Equals Operator

tomorrow >= today

Unicode Greater Equals Operator

tomorrow ≥ today

==== Duration and Period Comparison Operators (since 2.0.0)

Duration and Period can be compared with each other values of the same type.

Operator Name Example

<

Less Operator

one_week < two_weeks

<=

Less Equals Operator

one_minute <= two_minutes_warning

Unicode Less Equals Operator

one_second ≤ two_minutes_warning

>

Greater Operator

one_year > half_year

>=

Greater Equals Operator

one_week >= one_wek

Unicode Greater Equals Operator

thirty_seconds ≥ thirty_seconds

==== Version Comparison Operators

Two versions are compared with each other according to the Semantic Versioning schema.

Operator Name Example

<

Less Operator

'1.0.0'?version < .version

<=

Less Equals Operator

'1.0.0'?version <= .version

Unicode Less Equals Operator

'1.0.0'?version ≤ .version

>

Greater Operator

'1.0.0'?version > .version

>=

Greater Equals Operator

'1.0.0'?version >= .version

Unicode Greater Equals Operator

'1.0.0'?version ≥ .version

=== Multiplicative Operators

Since FreshMarker 1.6.3 model types can overload these operators.
Operator Name Example

*

Multiplication Operator

x * 2

×

Unicode Multiplication Operator

x × 2

/

Division Operator

x / 3

÷

Unicode Division Operator

x ÷ 3

%

Remainder Operator

x % 4

=== Additive Operators

Since FreshMarker 1.6.3 model types can overload these operators. In addition to the additive operators for the numeric types and the string concatenation, there are some overloaded operators for date and period.

Operator Name Example Description

+

String Concatenation Operator

x + ' '

+

Numerical Addition Operator

x + 3

-

Numerical Subtraction Operator

x - 4

+

Temporal Addition Operator

date + period

add the period to the given date. This operation is not a commutativity. (experimental)

-

Temporal Subtraction Operator

date - period

subtract the period from the given date. This operation is not a commutativity. (experimental)

+

Temporal Addition Operator

date + 3

add the amount of days to the given date. This operation is not a commutativity. (experimental)

-

Temporal Subtraction Operator

date - 7

subtract the amount of days from the given date. This operation is not a commutativity. (experimental)

+

Period Addition Operator

period + period

add the period to the given period. (experimental)

-

Period Subtraction Operator

period - period

subtract the period from the given period. (experimental)

+

Period Addition Operator

period + 3

add the amount of days to the given period. This operation is not a commutativity. (experimental)

-

Period Subtraction Operator

period - 7

subtract the amount of days from the given period. This operation is not a commutativity. (experimental)

+

Year Addition Operator

year + 3

add the amount of years to the given year. This operation is not a commutativity. (experimental)

-

Year Subtraction Operator

year - 7

subtract the amount of years from the given year. This operation is not a commutativity. (experimental)

+

List Concatenation Operator

list + list

Concatenate the lists to a new list. (experimental)

=== Negation

Operator Description Example

!

Logical Not Operator

!false

¬

Unicode Logical Not Operator

¬true

=== Unary Numerical Operators

Operator Name Example

+

Unary Plus Operator

+4

-

Unary Minus Operator

-4

=== Default Operator

Returns true if the operant is not NULL.

Operator Description Example

!

Default Operator

value!

!

Default Operator with parameter

value!"default"

=== Dot Key Operator

TBD

=== Dynamic Key Operator

==== Slicing

TBD

=== Built-In Operator

The Built-In operator calls type-specific functions, so-called Built-Is, on an expression. If the specified operator does not exist for the specific type, processing is terminated with an error.

The built-in operator is available in the default ? and a null-safe variant ?!.

With the null-safe variant, the processing of NULL and Optional values is less restrictive. Instead of an error, the built-ins return their input value here.

This processing can also be configured for the default variant using a Feature.

Operator Description Example

?

Built-In Operator

?upper_case

?!

Null-aware Built-In Operator

?!lower_case

==== Existence Operator

Returns true if the operant is not NULL.

Operator Description Example

??

Existence Operator

value??

Unicode Existence Operator

value∃

== Customization

=== Customization by Configuration

==== Register Simple Mapping

Unknown Java classes are usually treated like beans. However, this is not desired in many cases. If the ToString representation of an object is to be used in the output, its class can be registered via registerSimpleMapping(Class<?> type) for this purpose.

Example with Leitweg-ID and toString method
Configuration config = new Configuration();
config.registerSimpleMapping(LeitwegId.class);
TemplateBuilder templateBuilder = config.builder();
Template template = templateBuilder.getTemplate("id", "ID: ${id}!");
LeitwegId leitwegId = LeitwegId.parse("04011000-1234512345-06");
String resullt = template.process(Map.of("id", leitwegId));
Result
ID: 04011000-1234512345-06!

Alternatively, the method registerSimpleMapping(Function<Object, String> mapping) can be used if further adjustments are to be made.

Example with java.util.BitSet
Configuration config = new Configuration();
configuration.registerSimpleMapping(BitSet.class, x -> {
  BitSet bitSet = (BitSet)x;
  StringBuilder builder = new StringBuilder();
  for (int i = 0, n = bitSet.length(); i < n; i++) {
    builder.append(bitSet.get(i) ? "●" : "○").append("\u2009");
  }
  return builder.toString();
});
Result for the BitSet 010110011
○ ● ○ ● ● ○ ○ ● ●

=== Custom Formatter

The default formatting for number and temporal values can be changed with the Configuration#registerFormatter(String, String) method.

The first parameter type specifies the value type number, zoned-date-time (for Instant and ZonedDateTime), offset-date-time (for OffsetDateTime), date-time (for LocalDateTime), date (for LocalDate) and time (for LocalTime). The second parameter is a pattern of DecimalFormatter or DateTimeFormatter.

Example with custom number and temporal formatting
Configuration config = new Configuration();
config.registerFormatter("number", "#.00");
TemplateBuilder templateBuilderWithNumber = config.builder();
config.registerFormatter("date", "dd. MM. yyyy");
config.registerFormatter("time", "HH:mm");
TemplateBuilder templateBuilderWithNumberAndTemporal = config.builder();

TBD

=== Customization by TemplateBuilder

TBD

==== With Features (since 1.7.0)

The with and without methods can be used to modify the behaviour of FreshMarker. The parameter of the methods is a feature constant to activate or deactivate the corresponding feature.

===== Include Directive Features

These features are controlled by constants in the IncludeDirectiveFeature class.

Feature Description Initial enabled

ENABLED

Feature that determines whether include directives are evaluated.

false

LIMIT_INCLUDE_LEVEL

Feature that determines whether the level of includes is limited.

true

IGNORE_LIMIT_EXCEEDED_ERROR

Feature that determines whether the exceeded level of includes is an error.

false

PARSE_BY_DEFAULT

Feature that determines whether the content of include directives is parsed by default.

true

Example change limit for the include level
TemplateBuilder builder = new Configuration().builder()
    .with(IncludeDirectiveFeature.LIMIT_INCLUDE_LEVEL, 2);

===== Switch Directive Features

Constants in the SwitchDirectiveFeature class control these features.

Feature Description Initial enabled

ALLOW_ONLY_CONSTANT_CASES

Feature that determines whether case expressions must be constants.

false

ALLOW_ONLY_CONSTANT_ONS

Feature that determines whether on expressions must be constants.

false

ALLOW_ONLY_EQUAL_TYPE_CASES

Feature that determines whether all case expressions must contain constants from the same type.

false

ALLOW_ONLY_EQUAL_TYPE_ONS

Feature that determines whether all on expressions must contain constants from the same type.

false

OPTIMIZE_CONSTANT_SWITCH

Feature that determines whether the switch optimized with a map (since 1.11.0).

false

ERROR_ON_DUPLICATE_CASE_EXPRESSION

Feature that determines whether a duplicate case expression results in an error (since 1.11.0) on optimized constant switch directives.

false

===== Builtin Handling Features (since 1.7.4)

These features are controlled by constants in the BuiltinHandlingFeature class.

Feature Description Initial enabled

IGNORE_OPTIONAL_EMPTY

Feature that ignores errors due to empty optionals on built-ins.

false

IGNORE_NULL

Feature that ignores errors due to NULL values on built-ins.

false

===== Partial Reduction Features (since 1.10.0)

These features are controlled by constants in the ReductionFeature class.

Feature Description Initial enabled

UNFOLD_LIST

Feature that allows template reduction by unrolling list directives. Feature that determines whether list directives are unrolled. Only list directives that do not exceed a configurable number of elements are unrolled. The default value is 5.

false

MERGE_CONSTANT_FRAGMENTS

Feature that determines whether constant fragments are merged.

false

Example change maximum element number for unrolling lists
TemplateBuilder builder = new Configuration().builder()
    .with(ReductionFeature.UNFOLD_LIST, 7);

==== With Clock (since 1.6.9)

The withClock method can be used to create a DefaultTemplateBuilder that generates templates with a modified clock. This allows templates to generate an output as it would have looked at a different time. This applies in particular to the built-in variable .now.

Example builder with fixed clock in the past
Configuration config = new Configuration();
Instant instant = ZonedDateTime.of(1968, 8, 24, 12, 30, 0, 0,ZoneOffset.UTC).toInstant();
TemplateBuilder builder = config.builder().withClock(Clock.fixed(instant, ZoneOffset.UTC));

==== With Locale

TBD

Example builder with german locale
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withLocale(Locale.GERMANY);

==== With Zone-ID

TBD

Example builder with "Europe/Berlin" Zone-ID
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withZoneId(ZoneId.of("Europe/Berlin"));

==== With Date-Time Format

TBD

Example builder with custom date-time format
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withDateTimeFormat("short");

==== With Date Format

TBD

Example builder with custom date format
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withDateFormat("dd. MMMM yyyy");

==== With Time Format

TBD

Example builder with custom time format
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withTimeFormat("medium");

==== With Output-Format

TBD

Example builder with AsciiDoctor Output-Format
Configuration config = new Configuration();
TemplateBuilder builder = config.builder().withOutputFormat(StandardOutputFormats.ADOC);

== Extension API (since 1.8.0)

=== Overview

The Extension mechanism replaces the previous PlugInProvider mechanism. Although the previous plug-in mechanism worked well, it shows some weaknesses when extending FreshMarker.

=== Registering Extensions

Extensions can be registered automatically via the Java ServiceLoader mechanism or programmatically.

==== Automatic Extension Registration

Extensions can be registered automatically via the Java ServiceLoader mechanism. This allows third-party Extensions to be automatically recognized and registered, depending on what is available in the classpath.

Specifically, a custom Extension can be registered by specifying its fully qualified class name in a file named org.freshmarker.api.extension.Extension in the /META-INF/services folder in the associated JAR file.

==== Programmatic Extension Registration

Developers can register Extension programmatically by calling the Configuration#register(Extension) method.

When an Extension is registered automatically, it can only be configured via template features. In contrast, when an Extension is registered programmatically, it can be modified via constructor arguments or setter methods.

==== Extension Order

The Extension are registered in a deterministic order. The default Extension are registered first, followed by the automatic Extension and then the programmatic Extension.

=== Feature Handling

TemplateFeatureProvider defines the API for Extensions that wish to add new Freshmarker template features.

Template features are used to modify the behavior of FreshMarker or third-party Extensions.

Each template feature is a constant in an enum implementing the TemplateFeature interface. A template feature is enabled by default, this can be changed by overriding the isEnabledByDefault method.

Example TemplateFeature
public enum ExampleFeature implements TemplateFeature {
    FIRST, SECOND
}
Example TemplateFeature with a disabled template feature
public enum ExampleFeature implements TemplateFeature {
    FIRST, SECOND;
    boolean isEnabledByDefault() {
        return this == FIRST;
    }
}

The enum ExampleFeature provides two template features. They can be evaluated via a provided FeatureSet instance.

Example TemplateFeature usage
private boolean firstEnabled;
private boolean secondDisabled;

public init(FeatureSet featureSet) {
    firstEnabled = featureSet.isEnabled(ExampleFeature.FIRST);
    secondDisabled = featureSet.isDisabled(ExampleFeature.SECOND);
}

Before a template feature can be used, it must first be registered with FreshMarker. There are two ways to do this. The TemplateFeature can be added to the constructor of the Configuration class or a custom TemplateFeatureProvider can be registered.

Example TemplateFeatureProvider
public class ExampleFeatureProvider implements TemplateFeatureProvider {
    @Override
    public Set<TemplateFeature> provideFeatures() {
        return EnumSet.allOf(ExampleFeature.class);
    }
}

After registration, the features are available for all Extension. In order for an Extension to react to a feature, it must implement the Extension#init(FeatureSet) method.

The TemplateFeatureProviders are already evaluated during registration, while the other extensions are first evaluated for the template builder and then for the template. All available features are then available for evaluation.

=== Additional Built-Ins

BuiltInProvider defines the API for Extensions that wish to add new built-ins.

Each built-in must implement the BuiltIn interface.

Example BuiltIn
public class ExampleBuiltIn implements BuiltIn {
    public TemplateObject apply(TemplateObject value, List<TemplateObject> parameters, ProcessContext context) {
        new TemplateString("EXAMPLE: " + string);
    }
}
Example BuiltIn as Lambda
(value, parameters, context) -> new TemplateString("EXAMPLE: " + value);

Since each BuiltIn is assigned to a model type and has a name, the BuiltInProvider returns the built-ins as Register<Class<? extends TemplateObject>, String, BuiltIn>.

The BuiltInRegister is a Register implementation and can be used in a custom BuiltInProvider.

Usage of BuiltInRegister
public final class ExampleBuiltInProvider implements BuilInProvider {
    @Override
    public Register<Class<? extends TemplateObject>, String, BuiltIn> provideBuiltInRegister() {
        BuiltInRegister register = new BuiltInRegister();
        register.add(TemplateString.class, "example", new ExampleBuiltIn());
        return register;
    }
}

=== Additional Built-In Variables

The BuiltInVariableProvider defines the API for Extensions that wish to add new built-in variables.

Each custom built-in variable has to implement the BuiltInVariable interface.

Example BuiltInVariable
public class ExampleBuiltInVariable implements BuiltInVariable {
    public TemplateObject apply(ProcessContext context) {
        new TemplateString("EXAMPLE");
    }
}
Example BuiltInVariable as Lambda
(context) -> new TemplateString("EXAMPLE");

Since each BuiltInVariable has a name, the BuiltInVariableProvider returns the built-in variables as Map<String,BuiltInVariable>.

Example BuiltInVariableProvider
public final class ExampleBuiltInProvider implements BuilInProvider {
    public Map<String,BuiltInVariable> provideBuiltIns() {
        Map.of("example", (context) -> new TemplateString("EXAMPLE"));
    }
}

=== Additional User Directives

The UserDirectiveProvider defines the API for Extensions that wish to add new user directives.

Each custom user directive hast to implement the UserDirective interface.

Example UserDirective
public class ExampleUserDirective implements UserDirective {
    public execute(ProcessContext context, Map<String, TemplateObject> args, Fragment body) {
        body.process(context);
        body.process(context);
    }
}
Example UserDirective as Lambda
(context, args, body) -> { body.process(context); body.process(context); };

Since each UserDirective has a name, the UserDirectiveProvider returns the user directives as Map<String,UserDirective>.

Example UserDirectiveProvider
public final class ExampleUserDirectiveProvider implements UserDirectiveProvider {
    public Map<String,UserDirective> provideUserDirectives() {
        Map.of("example", new ExampleUserDirective());
    }
}

TBD

=== Additional Functions

The FunctionProvider defines the API for Extensions that wish to add new template functions.

Each custom template function hast to implement the TemplateFunction interface.

Example TemplateFunction
public class ExampleFunction implements TemplateFunction {
    public TemplateObject execute(ProcessContext context, List<TemplateObject> args) {
        return TemplateNumber.of(args.size());
    }
}
Example UserDirective as Lambda
(context, args) -> TemplateNumber.of(args.size());

Since each TemplateFunction has a name, the FunctionProvider returns the user directives as Map<String,TemplateFunction>.

Example FunctionProvider
public final class ExampleFunctionProvider implements UserDirectiveProvider {
    public Map<String,TemplateFunction> provideFunctions() {
        Map.of("example", (context, args) -> TemplateNumber.of(args.size()));
    }
}

=== Additional Formatter

The FormatterProvider defines the API for Extensions that wish to add new formatter.

TBD

=== Additional Type Mapper

The TypeMapperProvider defines the API for Extensions that wish to add new type mapper. The type mappers are used in the MappingTemplateObjectProvider to create template-model objects from Java objects.

The MappingTemplateObjectProvider ist the first TemplateObjectProvider in the chain of template-object providers. Its task is to create corresponding template-model objects for the registered primitive types. Other TemplateObjectProviders that follow in the chain are used to process records, beans and collection and custom types.

Example TypeMapper
public class ExampleTypeMapper implements TypeMapper {
    public TemplateObject apply(Object o) {
        Dimension d = (Dimension) o;
        return new TemplateString(d.width + "x" + d.height);
    }
}
Example TypeMapper als Lambda
value -> {
    Dimension d = (Dimension) o;
    return new TemplateString(d.width + "x" + d.height);
};

The parameter can be cast to the desired type in the apply method, as the TypeMapper is only called exactly for this type.

Since each TypeMapper has a type, the TypeMapperProvider returns the type-mappers as Map<Class<?>, TypeMapper>.

Example TypeMapperProvider
public final class ExampleBuiltInProvider implements BuilInProvider {
    public Map<String,BuiltInVariable> provideBuiltIns() {
        Map.of(Dimension.class, new ExampleTypeMapper());
    }
}

=== Additional Template-Object Provider

The TemplateObjectProviders defines the API for Extensions that wish to add new template-object providers.

TBD

=== Additional Output Formats

The OutputFormatProvider defines the API for Extensions that wish to add new output formats.

Example OutputFormatProvider
public final class OwaspOutputFormatProvider implements OutputFormatProvider {
  return Map.of("OWASP", new OutputFormat() {
      @Override
      public TemplateString escape(TemplateString value) {
        return new TemplateString(Sanitizers.FORMATTING.sanitize(value.getValue()));
      }
  });
}

1. This is an example in the introduction, sometimes it’s different, of course.
2. The library Holidays adds Built-Ins for Holidays
3. The library Telephone adds the InternationalPhoneNumber support.
4. The Java definition of whitespace, which does not include the non-breakable space, for example.
5. But you have to handle the null value, of course
6. Not for the legacy temporal types
7. Only for java.time.LocalDateTime
8. feature for switch directives exists since version 1.7.1
9. Usually, this is called a template fragment, but the technical FreshMarker template component classes are already called fragments.