freshmarker

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>1.7.5</version>
</dependency>
Gradle
implementation 'de.schegge:freshmarker:1.7.5'
Gradle Kotlin
implementation("de.schegge:freshmarker:1.7.5")
Mill
ivy"de.schegge:freshmarker:1.7.5"

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 Template instance is not thread safe, so it must not be shared between different threads. The result is unpredictable.

Also, the model must 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?uppercase} 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.

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.

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
<!-- title = "<script>alert("hallo")</script>"; -->
<h1>${title}</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

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 employee as e>${e.firstname}.${e.lastname}@${company.domain}</#list>
   """);
Template reducedTemplate = template.reduce("company", new Company("schegge.de"));

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.

A reduction cannot be carried out if loop variables, the default or exist operator are used. Variables and parameter should not hide model variables, this could be produced unpredicted results.

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.

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

yes

DateTime

Instant, ZonedDateTime, 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

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.

Ranges and Slices

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.

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, List, and String 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.

List Slices

TBD

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

Range Slices

TBD

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

TBD

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

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.orElseGet(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.

Forbidden Types

Although many Java types are supported directly or as a Java Bean, some classes cannot be used. For security reasons, all classes from the java, javax, sun and com.sun packages are ignored.

if you desperately need one of these classes, you can declare it as an exception.

Example allowed system classes
configuration.getSecurity().addAllowedClass(Runtime.class);
configuration.getSecurity().addAllowedPackages(java.lang.annotation.Annotation.class);
configuration.getSecurity().addAllowedPackages("java.io");
Of course, it only makes sense to allow classes that can also be interpreted by FreshMarker. Alternatively, a class can also be integrated via Custom String Types or a Plug-in.

In the example above, the class java.lang.Runtime and all classes in the packages java.lang.annotation and java.io and all of their subpackages are allowed.

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, an experimental alternative built-in operator can also 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()));

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 is 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 is 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 is 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 is 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

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.[4]

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.[4]

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.[4]

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'?endsWith('Dog')}

true

${'The quick brown Fox jumps over the lazy Dog'?ends_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

${'true'?length}

4

${'false'?length}

5

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!'-'}

-

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

string Built-In

This built-in returns its first string parameter if the value is true or the second string parameter if the value is false. The string Built-In is replaceable by the then Built-In.

Interpolation Output

${true?string('Fox', 'Dog')}

Fox

${false?string('Fox', 'Dog')}

Dog

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}

Date-Time Built-Ins

The Built-Ins in the examples operate on the java.time.Instant, java.time.ZonedDateTime, 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. [5]

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. [5]

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.[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

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. [5]

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. [5]

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. [5]

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. [5]

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.[5]

Interpolation Output Type

${temporal?year}

1968

year

month Built-In (since 1.7.0)

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

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.[5]

Interpolation Output Type

${temporal?day}

24

number

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. [5]

Interpolation Output Type

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

24. August 1968

string

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

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

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

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

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

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.4.0</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.3.0</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

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.[7]

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.[7]

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 Directive

The Var and Set 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. Later, the variable can be assigned new values with the Set Directive. A variable only exists within its context.

<#var test='eins'/>
<#set test='zwei'/>
<#set test='drei'/>

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.

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}

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[8] 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

Since FreshMarker 1.6.3 model types can overload these 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

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

Version Comparison Operators

These operators are in experimental stage.
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

TBD

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

The registerSimpleMapping method assigns a String mapping to a class. If no mapping is specified, the toString method of the class is used.

Example simple mapping for 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();
});

The example Mapping generates the String ○ ● ○ ● ● ○ ○ ● ●  for the BitSet 010110011.

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

Switch Directive Features

These features are controlled by constants in the SwitchDirectiveFeature class.

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

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 empty optionals on built-ins.

false

IGNORE_NULL

Feature that ignores errors due null values on built-ins.

false

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 Output-Format

TBD

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

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), 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

Custom String Types

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.

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

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));
// ID: 04011000-1234512345-06!
Example with Leitweg-ID and converting Function
Configuration config = new Configuration();
config.registerSimpleMapping(LeitwegId.class, lid -> "<<" + lid + ">>");
TemplateBuilder templateBuilder = config.builder();
Template template = templateBuilder.getTemplate("id", "${id}!");
LeitwegId leitwegId = LeitwegId.parse("04011000-1234512345-06");
String resullt = template.process(Map.of("id", leitwegId));
// ID: <<04011000-1234512345-06>>!

Custom OutputFormat

TBD

    default TemplateString escape(TemplateString value) {
        return value;
    }

    default TemplateString comment(TemplateString value) {
        return TemplateString.EMPTY;
    }
}

Custom primitive Model Types

Evaluate Value

TBD

Type Mapper

TBD

Operator Overloading

TBD

Custom Plug-Ins

The Plug-in mechanism is the core of FreshMarker's modular extension concept. Plug-ins can be used to add new types, formatters, functions, user directives, built-ins and mappers.

It is possible to create completely new template types or to add further features to existing ones.

In addition to the existing plug-ins for the FreshMarker basic types, you can also develop your own Plug-ins.

Plug-ins can be registered with FreshMarker via the ServiceLoader mechanism or via the Configuration#registerPlugin method.

public interface PluginProvider {

  default void registerFeature(TemplateFeatures features) {

  }

  default void registerMapper(Map<Class<?>, Function<Object, TemplateObject>> mapper) {

  }

  default void registerFormatter(Map<Class<? extends TemplateObject>, Formatter> formatter) {

  }

  default void registerFormatter(Map<Class<? extends TemplateObject>, Formatter> formatter, FeatureSet featureSet) {
    registerFormatter(formatter);
  }

  default void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns) {

  }

  default void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns, FeatureSet featureSet) {
    registerBuildIn(builtIns);
  }

  default void registerTemplateObjectProvider(List<TemplateObjectProvider> providers) {

  }

  default void registerUserDirective(Map<String, UserDirective> directives) {

  }

  default void registerFunction(Map<String, TemplateFunction> functions) {

  }

  default void registerBuiltInVariableProviders(Map<String, Function<ProcessContext, TemplateObject>> providers) {

  }
}

TBD

Custom Features

Every Custom Plug-in can add its own features to the Configuration

default void registerFeature(TemplateFeatures features) {

}

TBD


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. But you have to handle the null value of course
5. Not for the legacy temporal types
6. Only for java.time.LocalDateTime
7. feature for switch directives exists since version 1.7.1
8. Usually, this is called a template fragment, but the technical FreshMarker template component classes are already called fragments.