Qute Reference Guide

Qute is a templating engine designed specifically to meet the Quarkus needs. The usage of reflection is minimized to reduce the size of native images. The API combines both the imperative and the non-blocking reactive style of coding. In the development mode, all files located in the src/main/resources/templates folder are watched for changes and modifications are immediately visible in your application. Furthermore, Qute attempts to detect most of the template problems at build time and fail fast.

In this guide, you will find an introductory example, the description of the core features and Quarkus integration details.

Qute is primarily designed as a Quarkus extension. It is possible to use it as a "standalone" library too. However, in such case some of the features are not available. In general, any feature mentioned under the Quarkus Integration section is missing. You can find more information about the limitations and possibilities in the Qute Used as a Standalone Library section.

1. Hello World Example

In this example, we’d like to demonstrate the basic workflow when working with Qute templates. Let’s start with a simple hello world example. We will always need some template contents:

hello.html
<html>
  <p>Hello {name}! (1)
</html>
1 {name} is a value expression that is evaluated when the template is rendered.

Then, we will need to parse the contents into a template definition Java object. A template definition is an instance of io.quarkus.qute.Template.

If using Qute "standalone" you’ll need to create an instance of io.quarkus.qute.Engine first. The Engine represents a central point for template management with dedicated configuration. Let’s use the convenient builder:

Engine engine = Engine.builder().addDefaults().build();
In Quarkus, there is a preconfigured Engine available for injection - see Quarkus Integration.

Once we have an Engine instance we could parse the template contents:

Template helloTemplate = engine.parse(helloHtmlContent);
In Quarkus, you can simply inject the template definition. The template is automatically parsed and cached - see Quarkus Integration.

Finally, we will create a template instance, set the data and render the output:

// Renders <html><p>Hello Jim!</p></html>
helloTemplate.data("name", "Jim").render(); (1) (2)
1 Template.data(String, Object) is a convenient method that creates a template instance and sets the data in one step.
2 TemplateInstance.render() triggers a synchronous rendering, i.e. the current thread is blocked until the rendering is finished. However, there are also asynchronous ways to trigger the rendering and consume the results. For example there is the TemplateInstance.renderAsync() method that returns CompletionStage<String> or TemplateInstance.createMulti() that returns Mutiny’s Multi<String>.

So the workflow is simple:

  1. Create template contents (hello.html),

  2. Parse template definition (io.quarkus.qute.Template),

  3. Create template instance (io.quarkus.qute.TemplateInstance),

  4. Render output.

The Engine is able to cache the definitions so that it’s not necessary to parse the contents again and again. In Quarkus, the caching is done automatically.

2. Core Features

2.1. Basic Building Blocks

The dynamic parts of a template include comments, expressions, sections and unparsed character data.

2.1.1. Comments

A comment starts with {! and ends with !}, e.g. {! This is a comment !}. It could be multi-line and may contain expressions and sections: {! {#if true} !}. Of course, the content of a comment is completely ignored.

2.1.2. Expressions

An expression outputs an evaluated value. It consists of one or more parts. A part may represent simple properties: {foo}, {item.name} or virtual methods: {item.get(name)}, {name ?: 'John'}. An expression may start with a namespace: {inject:colors}.

2.1.3. Sections

A section may contain text, expressions and nested sections: {#if foo}{foo.name}{/if}. The name in the closing tag is optional: {#if active}ACTIVE!{/}. It can be empty: {#myTag image=true /}. A section may declare nested section blocks: {#if item.valid} Valid. {#else} Invalid. {/if} and decide which block to render.

2.1.4. Unparsed Character Data

It is used to mark the content that should be rendered but not parsed. It starts with {| and ends with |}: {| <script>if(true){alert('Qute is cute!')};</script> |}, and could be multi-line.

Previously, unparsed character data had to start with {[ and end with ]}. This syntax is still supported but we encourage users to switch to the new syntax to avoid some common collisions with constructs from other languages.

2.2. Identifiers and Tags

Identifiers are used in expressions and section tags. A valid identifier is a sequence of non-whitespace characters. However, users are encouraged to only use valid Java identifiers in expressions.

You can use bracket notation if you need to specify an identifier that contains a dot, e.g. {map['my.key']}.

When parsing a template document the parser identifies all tags. A tag starts and ends with a curly bracket, e.g. {foo}. The content of a tag must start with:

  • a digit, or

  • an alphabet character, or

  • underscore, or

  • a built-in command: #, !, @, /.

If it does not start with any of the above it is ignored by the parser.

Tag Examples
<html>
   <body>
   {_foo.bar}   (1)
   {! comment !}(2)
   {  foo}      (3)
   {{foo}}      (4)
   {"foo":true} (5)
   </body>
</html>
1 Parsed: an expression that starts with underscore.
2 Parsed: a comment
3 Ignored: starts with whitespace.
4 Ignored: starts with {.
5 Ignored: starts with ".
It is also possible to use escape sequences \{ and \} to insert delimiters in the text. In fact, an escape sequence is usually only needed for the start delimiter, ie. \{foo} will be rendered as {foo} (no parsing/evaluation will happen).

2.3. Removing Standalone Lines From the Template

By default, the parser removes standalone lines from the template output. A standalone line is a line that contains at least one section tag (e.g. {#each} and {/each}), parameter declaration (e.g. {@org.acme.Foo foo}) or comment but no expression and no non-whitespace character. In other words, a line that contains no section tag or a parameter declaration is not a standalone line. Likewise, a line that contains an expression or a non-whitespace character is not a standalone line.

Template Example
<html>
  <body>
     <ul>
     {#for item in items} (1)
       <li>{item.name} {#if item.active}{item.price}{/if}</li>  (2)
                          (3)
     {/for}               (4)
     </ul>
   <body>
</html>
1 This is a standalone line and will be removed.
2 Not a standalone line - contains an expression and non-whitespace characters
3 Not a standalone line - contains no section tag/parameter declaration
4 This is a standalone line.
Default Output
<html>
  <body>
     <ul>
       <li>Foo 100</li>

     </ul>
   <body>
</html>
In Quarkus, the default behavior can be disabled by setting the property quarkus.qute.remove-standalone-lines to false. In this case, all whitespace characters from a standalone line will be printed to the output.
Output with quarkus.qute.remove-standalone-lines=false
<html>
  <body>
     <ul>

       <li>Foo 100</li>


     </ul>
   <body>
</html>

2.4. Expressions

An expression outputs a value. It consists of one or more parts separated by dot (dot notation) or square brackets (bracket notation). In the object.property (dot notation) syntax, the property must be a valid identifier. In the object[property_name] (bracket notation) syntax, the property_name has to be a non-null literal value. An expression could start with an optional namespace followed by a colon (:). A valid namespace consist of alphanumeric characters and underscores.

Expressions Example
{name} (1)
{item.name} (2)
{item['name']} (3)
{global:colors} (4)
1 no namespace, one part: name
2 no namespace, two parts: item, name
3 equivalent to {item.name} but using the bracket notation
4 namespace global, one part: colors

A part of an expression could be a virtual method in which case the name can be followed by a list of comma-separated parameters in parentheses. A parameter of a virtual method can be either a nested expression or a literal value. We call it "virtual" because it does not have to be backed by a real Java method.

Virtual Methods Example
{item.getLabels(1)} (1)
{name or 'John'} (2)
1 no namespace, two parts - item, getLabels(1), the second part is a virtual method with name getLabels and params 1
2 infix notation that can be used for virtual methods with single parameter, translated to name.or('John'); no namespace, two parts - name, or('John')

2.4.1. Supported Literals

Literal Examples

boolean

true, false

null

null

string

'value', "string"

integer

1, -5

long

1l, -5L

double

1D, -5d

float

1f, -5F

2.4.2. Resolution

The first part of the expression is always resolved against the current context object. If no result is found for the first part it’s resolved against the parent context object (if available). For an expression that starts with a namespace the current context object is found using all the available NamespaceResolvers. For an expression that does not start with a namespace the current context object is derived from the position of the tag. All other parts of an expression are resolved using all ValueResolvers against the result of the previous resolution.

For example, expression {name} has no namespace and single part - name. The "name" will be resolved using all available value resolvers against the current context object. However, the expression {global:colors} has the namespace global and single part - colors. First, all available NamespaceResolvers will be used to find the current context object. And afterwards value resolvers will be used to resolve "colors" against the context object found.

Data passed to the template instance are always accessible using the data namespace. This could be useful to access data for which the key is overridden:

<html>
{item.name} (1)
<ul>
{#for item in item.getDerivedItems()} (2)
  <li>
  {item.name} (3)
  is derived from
  {data:item.name} (4)
  </li>
{/for}
</ul>
</html>
1 item is passed to the template instance as a data object.
2 Iterate over the list of derived items.
3 item is an alias for the iterated element.
4 Use the data namespace to access the item data object.

2.4.3. Current Context

If an expression does not specify a namespace the current context object is derived from the position of the tag. By default, the current context object represents the data passed to the template instance. However, sections may change the current context object. A typical example is the with section that could be used to set the current context object in order to simplify the template structure:

{#with item}
  <h1>{name}</h1>  (1)
  <p>{description}</p>
{/with}
1 name is resolved against the item.

Another built-in section that modifies the current context object is let/set:

{#let myParent=order.item.parent myPrice=order.price} (1)
  <h1>{myParent.name}</h1>
  <p>Price: {myPrice}</p>
{/let}
1 The current context object inside the section is the map of resolved parameters.
The current context can be accessed via the implicit binding this.

2.4.4. Built-in Resolvers

Name Description Examples

Elvis Operator

Outputs the default value if the previous part cannot be resolved or resolves to null.

{person.name ?: 'John'}, {person.name or 'John'}, {person.name.or('John')}

orEmpty

Outputs an empty list if the previous part cannot be resolved or resolves to null.

{#for pet in pets.orEmpty}{pet.name}{/for}

Ternary Operator

Shorthand for if-then-else statement. Unlike in If Section nested operators are not supported.

{item.isActive ? item.name : 'Inactive item'} outputs the value of item.name if item.isActive resolves to true.

Logical AND Operator

Outputs true if both parts are not falsy as described in the If Section. The parameter is only evaluated if needed.

{person.isActive && person.hasStyle}

Logical OR Operator

Outputs true if any of the parts is not falsy as described in the If Section. The parameter is only evaluated if needed.

{person.isActive || person.hasStyle}

The condition in a ternary operator evaluates to true if the value is not considered falsy as described in the If Section.
In fact, the operators are implemented as "virtual methods" that consume one parameter and can be used with infix notation. For example {person.name or 'John'} is translated to {person.name.or('John')} and {item.isActive ? item.name : 'Inactive item'} is translated to {item.isActive.ifTruthy(item.name).or('Inactive item')}

2.4.5. Arrays

You can iterate over elements of an array with the Loop Section. Moreover, it’s also possible to get the length of the specified array and access the elements directly via an index value. Additionaly, you can access the first/last n elements via the take(n)/takeLast(n) methods.

Array Examples
<h1>Array of length: {myArray.length}</h1> (1)
<ul>
  <li>First: {myArray.0}</li> (2)
  <li>Second: {myArray[1]}</li> (3)
  <li>Third: {myArray.get(2)}</li> (4)
</ul>
<ol>
 {#for element in myArray}
 <li>{element}</li>
 {/for}
</ol>
First two elements: {#each myArray.take(2)}{it}{/each} (5)
1 Outputs the length of the array.
2 Outputs the first element of the array.
3 Outputs the second element of the array using the bracket notation.
4 Outputs the third element of the array via the virtual method get().
5 Outputs the first two elements of the array.

2.4.6. Character Escapes

For HTML and XML templates the ', ", <, >, & characters are escaped by default if a template variant is set.

In Quarkus, a variant is set automatically for templates located in the src/main/resources/templates. By default, the java.net.URLConnection#getFileNameMap() is used to determine the content type of a template file. The additional map of suffixes to content types can be set via quarkus.qute.content-types.

If you need to render the unescaped value:

  1. Either use the raw or safe properties implemented as extension methods of the java.lang.Object,

  2. Or wrap the String value in a io.quarkus.qute.RawString.

<html>
<h1>{title}</h1> (1)
{paragraph.raw} (2)
</html>
1 title that resolves to Expressions & Escapes will be rendered as Expressions &amp; Escapes
2 paragraph that resolves to <p>My text!</p> will be rendered as <p>My text!</p>

2.4.7. Virtual Methods

A virtual method is a part of an expression that looks like a regular Java method invocation. It’s called "virtual" because it does not have to match the actual method of a Java class. In fact, like normal properties a virtual method is also handled by a value resolver. The only difference is that for virtual methods a value resolver consumes parameters that are also expressions.

Virtual Method Example
<html>
<h1>{item.buildName(item.name,5)}</h1> (1)
</html>
1 buildName(item.name,5) represents a virtual method with name buildName and two parameters: item.name and 5 . The virtual method could be evaluated by a value resolver generated for the following Java class:
class Item {
   String buildName(String name, int age) {
      return name + ":" + age;
   }
}
Virtual methods are usually evaluated by value resolvers generated for @TemplateExtension methods, @TemplateData or classes used in parameter declarations. However, a custom value resolver that is not backed by any Java class/method can be registered as well.

A virtual method with single parameter can be called using the infix notation:

Infix Notation Example
<html>
<p>{item.price or 5}</p>  (1)
</html>
1 item.price or 5 is translated to item.price.or(5).

Virtual method parameters can be "nested" virtual method invocations.

Nested Virtual Method Example
<html>
<p>{item.subtractPrice(item.calculateDiscount(10))}</p>  (1)
</html>
1 item.calculateDiscount(10) is evaluated first and then passed as an argument to item.subtractPrice().

2.4.8. Evaluation of CompletionStage and Uni Objects

Objects that implement java.util.concurrent.CompletionStage and io.smallrye.mutiny.Uni are evaluated in a special way. If a part of an expression resolves to a CompletionStage, the resolution continues once this stage is completed and the next part of the expression (if any) is evaluated against the result of the completed stage. For example, if there is an expression {foo.size} and foo resolves to CompletionStage<List<String>> then size is resolved against the completed result, i.e. List<String>. If a part of an expression resolves to a Uni, a CompletionStage is first created from Uni using Uni#subscribeAsCompletionStage() and then evaluated as described above.

2.4.9. Missing Properties

It can happen that an expression may not be evaluated at runtime. For example, if there is an expression {person.age} and there is no property age declared on the Person class. Qute outputs the special constant NOT_FOUND in this case by default. However, it’s possible to change the default strategy via the quarkus.qute.property-not-found-strategy as described in the Configuration Reference.

Similar errors are detected at build time if Type-safe Expressions and Type-safe Templates are used.
Alternatively, the elvis operator can be used to output the default value in case the property cannot be resolved or resolves to null: {person.age ?: 10} would output 10. And for the loop section, the orEmpty resolver can be used. See also Built-in Resolvers.

2.5. Sections

A section:

  • has a start tag

    • starts with #, followed by the name of the section such as {#if} and {#each},

  • may be empty

    • tag ends with /, ie. {#emptySection /}

  • may contain other expression, sections, etc.

    • the end tag starts with / and contains the name of the section (optional): {#if foo}Foo!{/if} or {#if foo}Foo!{/},

The start tag can also define parameters. The parameters have optional names. A section may contain several content blocks. The "main" block is always present. Additional/nested blocks also start with # and can have parameters too - {#else if item.isActive}. A section helper that defines the logic of a section can "execute" any of the blocks and evaluate the parameters.

{#if item.name is 'sword'}
  It's a sword!
{#else if item.name is 'shield'}
  It's a shield!
{#else}
  Item is neither a sword nor a shield.
{/if}

2.5.1. Loop Section

The loop section makes it possible to iterate over an instance of Iterable, Iterator, array, Map 's entry set, Stream and an Integer. It has two flavors. The first one is using the each name and it is an implicit alias for the iteration element.

{#each items}
  {it.name} (1)
{/each}
1 name is resolved against the current iteration element.

The other form is using the for name and can specify the alias used to reference the iteration element:

{#for item in items}
  {item.name}
{/for}

It’s also possible to access the iteration metadata inside the loop:

{#each items}
  {count}. {it.name} (1)
{/each}
1 count represents one-based index.
The iteration metadata also include zero-based index, hasNext, odd and even properties.

The for statement also works with integers, starting from 1. In the example below, considering that total = 3:

{#for i in total}
  {i}:
{/for}

And the output will be:

1:2:3:

A loop section may define the {#else} block that is executed when there are no items to iterate:

{#for item in items}
  {item.name}
{#else}
  No items.
{/for}

2.5.2. If Section

The if section represents a basic control flow section. The simplest possible version accepts a single parameter and renders the content if the condition is evaluated to true. A condition without an operator evaluates to true if the value is not considered falsy, i.e. if the value is not null, false, an empty collection, an empty map, an empty array, an empty string/char sequence or a number equal to zero.

{#if item.active}
  This item is active.
{/if}

You can also use the following operators in a condition:

Operator Aliases Precedence (higher wins)

logical complement

!

4

greater than

gt, >

3

greater than or equal to

ge, >=

3

less than

lt, <

3

less than or equal to

le, <=

3

equals

eq, ==, is

2

not equals

ne, !=

2

logical AND (short-circuiting)

&&, and

1

logical OR (short-circuiting)

||, or

1

A simple operator example
{#if item.age > 10}
  This item is very old.
{/if}

Multiple conditions are also supported.

Multiple conditions example
{#if item.age > 10 && item.price > 500}
  This item is very old and expensive.
{/if}

Precedence rules can be overridden by parentheses.

Parentheses example
{#if (item.age > 10 || item.price > 500) && user.loggedIn}
  User must be logged in and item age must be > 10 or price must be > 500.
{/if}

You can also add any number of else blocks:

{#if item.age > 10}
  This item is very old.
{#else if item.age > 5}
  This item is quite old.
{#else if item.age > 2}
  This item is old.
{#else}
  This item is not old at all!
{/if}

2.5.3. When/Switch Section

This section is similar to Java’s switch or Kotlin’s when constructs. It matches a tested value against all blocks sequentially until a condition is satisfied. The first matching block is executed. All other blocks are ignored (this behavior differs to the Java switch where a break statement is necessary).

Example using the when/is name aliases
{#when items.size}
  {#is 1} (1)
    There is exactly one item!
  {#is > 10} (2)
    There are more than 10 items!
  {#else} (3)
    There are 2 -10 items!
{/when}
1 If there is exactly one parameter it’s tested for equality.
2 It’s possible to use an operator to specify the matching logic. Unlike in the If Section nested operators are not supported.
3 else is block is executed if no other block matches the value.
Example using the switch/case name aliases
{#switch person.name}
  {#case 'John'} (1)
    Hey John!
  {#case 'Mary'}
    Hey Mary!
{/switch}
1 case is an alias for is.

A tested value that resolves to an enum is handled specifically. The parameters of an is/case block are not evaluated as expressions but compared with the result of toString() invocation upon the tested value.

{#when machine.status}
  {#is ON}
    It's running. (1)
  {#is in OFF BROKEN}
    It's broken or OFF. (2)
{/when}
1 This block is executed if machine.status.toString().equals("ON").
2 This block is executed if machine.status.toString().equals("OFF") or machine.status.toString().equals("BROKEN").
An enum constant is validated if the tested value has a type information available and resolves to an enum type.

The following operators are supported in is/case block conditions:

Operator Aliases Example

not equal

!=, not, ne

{#is not 10},{#case != 10}

greater than

gt, >

{#case le 10}

greater than or equal to

ge, >=

{#is >= 10}

less than

lt, <

{#is < 10}

less than or equal to

le, <=

{#case le 10}

in

in

{#is in 'foo' 'bar' 'baz'}

not in

ni,!in

{#is !in 1 2 3}

2.5.4. With Section

This section can be used to set the current context object. This could be useful to simplify the template structure:

{#with item.parent}
  <h1>{name}</h1>  (1)
  <p>{description}</p> (2)
{/with}
1 The name will be resolved against the item.parent.
2 The description will be also resolved against the item.parent.

This section might also come in handy when we’d like to avoid multiple expensive invocations:

{#with item.callExpensiveLogicToGetTheValue(1,'foo',bazinga)}
  {#if this is "fun"} (1)
    <h1>Yay!</h1>
  {#else}
    <h1>{this} is not fun at all!</h1>
  {/if}
{/with}
1 this is the result of item.callExpensiveLogicToGetTheValue(1,'foo',bazinga). The method is only invoked once even though the result may be used in multiple expressions.

2.5.5. Let/Set Section

This section allows you to define named local variables:

{#let myParent=order.item.parent isActive=false age=10} (1)
  <h1>{myParent.name}</h1>
  Is active: {isActive}
  Age: {age}
{/let}
1 The local variable is initialized with an expression that can also represent a literal.

The section tag is also registered under the set alias:

{#set myParent=item.parent price=item.price}
  <h1>{myParent.name}</h1>
  <p>Price: {price}
{/set}

2.5.6. Include Section

This section can be used to include another template and possibly override some parts of the template (template inheritance).

Simple Example
<html>
<head>
<meta charset="UTF-8">
<title>Simple Include</title>
</head>
<body>
  {#include foo limit=10 /} (1)(2)
</body>
</html>
1 Include a template with id foo. The included template can reference data from the current context.
2 It’s also possible to define optional parameters that can be used in the included template.

Template inheritance makes it possible to reuse template layouts.

Template "base"
<html>
<head>
<meta charset="UTF-8">
<title>{#insert title}Default Title{/}</title> (1)
</head>
<body>
  {#insert}No body!{/} (2)
</body>
</html>
1 insert sections are used to specify parts that could be overridden by a template that includes the given template.
2 An insert section may define the default content that is rendered if not overridden. If no name parameter is supplied then the main block of the relevant {#include} section is used.
Template "detail"
{#include base} (1)
  {#title}My Title{/title} (2)
  <div> (3)
    My body.
  </div>
{/include}
1 include section is used to specify the extended template.
2 Nested blocks are used to specify the parts that should be overridden.
3 The content of the main block is used for an {#insert} section with no name parameter specified.
Section blocks can also define an optional end tag - {/title}.

2.5.7. Eval Section

This section can be used to parse and evaluate a template dynamically. The behavior is very similar to the Include Section but:

  1. The template content is passed directly, i.e. not obtained via an io.quarkus.qute.TemplateLocator,

  2. It’s not possible to override parts of the evaluated template.

{#eval myData.template name='Mia' /} (1)(2)(3)
1 The result of myData.template will be used as the template. The template is executed with the Current Context, i.e. can reference data from the template it’s included into.
2 It’s also possible to define optional parameters that can be used in the evaluated template.
3 The content of the section is always ignored.
The evaluated template is parsed and evaluated every time the section is executed. In other words, it’s not possible to cache the parsed value to conserve resources and optimize the performance.

2.5.8. User-defined Tags

User-defined tags can be used to include a template and optionally pass some parameters. Let’s suppose we have a template called itemDetail.html:

{#if showImage} (1)
  {it.image} (2)
  {nested-content} (3)
{/if}
1 showImage is a named parameter.
2 it is a special key that is replaced with the first unnamed param of the tag.
3 (optional) nested-content is a special key that will be replaced by the content of the tag.

Now if we register this template under the name itemDetail.html and if we add a UserTagSectionHelper to the engine:

Engine engine = Engine.builder()
                   .addSectionHelper(new UserTagSectionHelper.Factory("itemDetail","itemDetail.html"))
                   .build();
In Quarkus, all files from the src/main/resources/templates/tags are registered and monitored automatically!

We can include the tag like this:

<ul>
{#for item in items}
  <li>
  {#itemDetail item showImage=true} (1)
    = <b>{item.name}</b> (2)
  {/itemDetail}
  </li>
{/for}
</ul>
1 item is resolved to an iteration element and can be referenced using the it key in the tag template.
2 Tag content injected using the nested-content key in the tag template.

2.6. Rendering Output

TemplateInstance provides several ways to trigger the rendering and consume the result. The most straightforward approach is represented by TemplateInstance.render(). This method triggers a synchronous rendering, i.e. the current thread is blocked until the rendering is finished, and returns the output. By contrast, TemplateInstance.renderAsync() returns a CompletionStage<String> which is completed when the rendering is finished.

TemplateInstance.renderAsync() Example
template.data(foo).renderAsync().whenComplete((result, failure) -> { (1)
   if (failure == null) {
      // consume the output...
   } else {
      // process failure...
   }
};
1 Register a callback that is executed once the rendering is finished.

There are also two methods that return Mutiny types. TemplateInstance.createUni() returns a new Uni<String> object. If you call createUni() the template is not rendered right away. Instead, every time Uni.subscribe() is called a new rendering of the template is triggered.

TemplateInstance.createUni() Example
template.data(foo).createUni().subscribe().with(System.out::println);

TemplateInstance.createMulti() returns a new Multi<String> object. Each item represents a part/chunk of the rendered template. Again, createMulti() does not trigger rendering. Instead, every time a computation is triggered by a subscriber the template is rendered again.

TemplateInstance.createMulti() Example
template.data(foo).createMulti().subscribe().with(buffer:append,buffer::flush);
The template rendering is divided in two phases. During the first phase, which is asynchronous, all expressions in the template are resolved and a result tree is built. In the second phase, which is synchronous, the result tree is materialized, i.e. one by one the result nodes emit chunks that are consumed/buffered by the specific consumer.

2.7. Engine Configuration

2.7.1. Value Resolvers

Value resolvers are used when evaluating expressions. A custom io.quarkus.qute.ValueResolver can be registered programmatically via EngineBuilder.addValueResolver().

ValueResolver Builder Example
engineBuilder.addValueResolver(ValueResolver.builder()
    .appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
    .resolveSync(ctx -> (Long) ctx.getBase() * 10)
    .build());

2.7.2. Template Locator

Manual registration is sometimes handy but it’s also possible to register a template locator using EngineBuilder.addLocator(Function<String, Optional<Reader>>). This locator is used whenever the Engine.getTemplate() method is called and the engine has no template for a given id stored in the cache.

In Quarkus, all templates from the src/main/resources/templates are located automatically.

2.7.3. Content Filters

Content filters can be used to modify the template contents before parsing.

Content Filter Example
engineBuilder.addParserHook(new ParserHook() {
    @Override
    public void beforeParsing(ParserHelper parserHelper) {
        parserHelper.addContentFilter(contents -> contents.replace("${", "$\\{")); (1)
    }
});
1 Escape all occurences of ${.

2.7.4. Strict Rendering

The strict rendering enables the developers to catch insidious errors caused by typos and invalid expressions. If enabled then any expression that cannot be resolved, i.e. is evaluated to an instance of io.quarkus.qute.Results.NotFound, will always result in a TemplateException and the rendering is aborted. A NotFound value is considered an error because it basically means that no value resolver was able to resolve the expression correctly.

null is a valid value though. It is considered falsy as described in the If Section and does not produce any output.

Strict rendering is enabled by default. However, you can disable this functionality via io.quarkus.qute.EngineBuilder.strictRendering(boolean).

In Quarkus, a dedicated config property can be used instead: quarkus.qute.strict-rendering.

If you really need to use an expression which can potentially lead to a "not found" error, you can use default values and safe expressions in order to suppress the error. A default value is used if the previous part of an expression cannot be resolved or resolves to null. You can use the elvis operator to output the default value: {foo.bar ?: 'baz'}, which is effectively the same as the following virtual method: {foo.bar.or('baz')}. A safe expression ends with the ?? suffix and results in null if the expression cannot be resolved. It can be very useful e.g. in {#if} sections: {#if valueNotFound??}Only rendered if valueNotFound is truthy!{/if}. In fact, ?? is just a shorthand notation for .or(null), i.e. {#if valueNotFound??} becomes {#if valueNotFound.or(null)}.

3. Quarkus Integration

If you want to use Qute in your Quarkus application add the following dependency to your project:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-qute</artifactId>
</dependency>

In Quarkus, a preconfigured engine instance is provided and available for injection - a bean with scope @ApplicationScoped, bean type io.quarkus.qute.Engine and qualifier @Default is registered automatically. Moreover, all templates located in the src/main/resources/templates directory are validated and can be easily injected.

import io.quarkus.qute.Engine;
import io.quarkus.qute.Template;
import io.quarkus.qute.Location;

class MyBean {

    @Inject
    Template items; (1)

    @Location("detail/items2_v1.html") (2)
    Template items2;

    @Inject
    Engine engine; (3)
}
1 If there is no Location qualifier provided, the field name is used to locate the template. In this particular case, the container will attempt to locate a template with path src/main/resources/templates/items.html.
2 The Location qualifier instructs the container to inject a template from a path relative from src/main/resources/templates. In this case, the full path is src/main/resources/templates/detail/items2_v1.html.
3 Inject the configured Engine instance.

It’s also possible to contribute to the engine configuration via a CDI observer method.

EngineBuilder Observer Example
import io.quarkus.qute.EngineBuilder;

class MyBean {

    void configureEngine(@Observes EngineBuilder builder) {
       builder.addValueResolver(ValueResolver.builder()
                .appliesTo(ctx -> ctx.getBase() instanceof Long && ctx.getName().equals("tenTimes"))
                .resolveSync(ctx -> (Long) ec.getBase() * 10)
                .build());
    }
}

3.1. Template Variants

Sometimes it’s useful to render a specific variant of the template based on the content negotiation. This can be done by setting a special attribute via TemplateInstance.setAttribute():

class MyService {

    @Inject
    Template items; (1)

    @Inject
    ItemManager manager;

    String renderItems() {
       return items.data("items",manager.findItems()).setAttribute(TemplateInstance.SELECTED_VARIANT, new Variant(Locale.getDefault(),"text/html","UTF-8")).render();
    }
}
When using quarkus-resteasy-qute the content negotiation is performed automatically. See RESTEasy Integration.

3.2. Injecting Beans Directly In Templates

A CDI bean annotated with @Named can be referenced in any template through the inject namespace:

{inject:foo.price} (1)
1 First, a bean with name foo is found and then used as the base object.

All expressions using the inject namespace are validated during build. For the expression inject:foo.price the implementation class of the injected bean must either have the price property (e.g. a getPrice() method) or a matching template extension method must exist.

A ValueResolver is also generated for all beans annotated with @Named so that it’s possible to access its properties without reflection.
If your application serves HTTP requests you can also inject the current io.vertx.core.http.HttpServerRequest via the inject namespace, e.g. {inject:vertxRequest.getParam('foo')}.

3.3. Type-safe Expressions

Template expressions can be optionally type-safe. Which means that an expression is validated against the existing Java types and template extension methods. If an invalid/incorrect expression is found then the build fails.

For example, if there is an expression item.name where item maps to org.acme.Item then Item must have a property name or a matching template extension method must exist.

An optional parameter declaration is used to bind a Java type to expressions whose first part matches the parameter name. Parameter declarations are specified directly in a template.

Parameter Declaration Example
{@org.acme.Foo foo} (1)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
  <h1>{title}</h1> (2)
  Hello {foo.message.toLowerCase}! (3) (4)
</body>
</html>
1 Parameter declaration - maps foo to org.acme.Foo.
2 Not validated - not matching a param declaration.
3 This expression is validated. org.acme.Foo must have a property message or a matching template extension method must exist.
4 Likewise, the Java type of the object resolved from foo.message must have a property toLowerCase or a matching template extension method must exist.
A value resolver is automatically generated for all types used in parameter declarations so that it’s possible to access its properties without reflection.
Method parameters of type-safe templates are automatically turned into parameter declarations.

Note that sections can override names that would otherwise match a parameter declaration:

{@org.acme.Foo foo}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Qute Hello</title>
</head>
<body>
  <h1>{foo.message}</h1> (1)
  {#for foo in baz.foos}
    <p>Hello {foo.message}!</p> (2)
  {/for}
</body>
</html>
1 Validated against org.acme.Foo.
2 Not validated - foo is overridden in the loop section.

3.4. Type-safe Templates

You can also define type-safe templates in your Java code. If using templates in JAX-RS resources, you can rely on the following convention:

  • Organise your template files in the /src/main/resources/templates directory, by grouping them into one directory per resource class. So, if your ItemResource class references two templates hello and goodbye, place them at /src/main/resources/templates/ItemResource/hello.txt and /src/main/resources/templates/ItemResource/goodbye.txt. Grouping templates per resource class makes it easier to navigate to them.

  • In each of your resource class, declare a @CheckedTemplate static class Template {} class within your resource class.

  • Declare one public static native TemplateInstance method(); per template file for your resource.

  • Use those static methods to build your template instances.

ItemResource.java
package org.acme.quarkus.sample;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@Path("item")
public class ItemResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance item(Item item); (1) (2)
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(@PathParam("id") Integer id) {
        return Templates.item(service.findItem(id)); (3)
    }
}
1 Declare a method that gives us a TemplateInstance for templates/ItemResource/item.html and declare its Item item parameter so we can validate the template.
2 The item parameter is automatically turned into a parameter declaration and so all expressions that reference this name will be validated.
3 Make the Item object accessible in the template.
By default, the templates defined in a class annotated with @CheckedTemplate can only contain type-safe expressions, i.e. expressions that can be validated at build time. You can use @CheckedTemplate(requireTypeSafeExpressions = false) to relax this requirement.

You can also declare a top-level Java class annotated with @CheckedTemplate:

Top-level checked templates
package org.acme.quarkus.sample;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import io.quarkus.qute.CheckedTemplate;

@CheckedTemplate
public class Templates {
    public static native TemplateInstance hello(String name); (1)
}
1 This declares a template with path templates/hello.txt. The name parameter is automatically turned into a parameter declaration and so all expressions that reference this name will be validated.

Then declare one public static native TemplateInstance method(); per template file. Use those static methods to build your template instances:

HelloResource.java
package org.acme.quarkus.sample;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import io.quarkus.qute.TemplateInstance;

@Path("hello")
public class HelloResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return Templates.hello(name);
    }
}

3.5. Template Extension Methods

Extension methods can be used to extend the data classes with new functionality (to extend the set of accessible properties and methods) or to resolve expressions for a specific namespace. For example, it is possible to add computed properties and virtual methods.

A value resolver is automatically generated for a method annotated with @TemplateExtension. If a class is annotated with @TemplateExtension then a value resolver is generated for every non-private static method declared on the class. Method-level annotations override the behavior defined on the class. Methods that do not meet the following requirements are ignored.

A template extension method:

  • must not be private

  • must be static,

  • must not return void.

If there is no namespace defined the class of the first parameter that is not annotated with @TemplateAttribute is used to match the base object. Otherwise the namespace is used to match an expression.

The method name is used to match the property name by default. However, it is possible to specify the matching name with TemplateExtension#matchName(). A special constant - TemplateExtension#ANY - may be used to specify that the extension method matches any name. It is also possible to match the name against a regular expression specified in TemplateExtension#matchRegex(). In both cases, an additional string method parameter must be used to pass the property name. If both matchName() and matchRegex() are set the regular expression is used for matching.

Extension Method Example
package org.acme;

class Item {

    public final BigDecimal price;

    public Item(BigDecimal price) {
        this.price = price;
    }
}

@TemplateExtension
class MyExtensions {

    static BigDecimal discountedPrice(Item item) { (1)
        return item.getPrice().multiply(new BigDecimal("0.9"));
    }
}
1 This method matches an expression with base object of the type Item.class and the discountedPrice property name.

This template extension method makes it possible to render the following template:

{item.discountedPrice} (1)
1 item is resolved to an instance of org.acme.Item.

3.5.1. Method Parameters

An extension method may declare parameters. If no namespace is specified then the first parameter that is not annotated with @TemplateAttribute is used to pass the base object, i.e. org.acme.Item in the first example. If matching any name or using a regular expression then a string method parameter needs to be used to pass the property name. Parameters annotated with @TemplateAttribute are obtained via TemplateInstance#getAttribute(). All other parameters are resolved when rendering the template and passed to the extension method.

Multiple Parameters Example
@TemplateExtension
class BigDecimalExtensions {

    static BigDecimal scale(BigDecimal val, int scale, RoundingMode mode) { (1)
        return val.setScale(scale, mode);
    }
}
1 This method matches an expression with base object of the type BigDecimal.class, with the scale virtual method name and two virtual method parameters.
{item.discountedPrice.scale(2,mode)} (1)
1 item.discountedPrice is resolved to an instance of BigDecimal.

3.5.2. Namespace Extension Methods

If TemplateExtension#namespace() is specified then the extension method is used to resolve expressions with the given namespace. Template extension methods that share the same namespace are grouped in one resolver ordered by TemplateExtension#priority(). The first matching extension method is used to resolve an expression.

Namespace Extension Method Example
@TemplateExtension(namespace = "str")
public static class StringExtensions {

   static String format(String fmt, Object... args) {
      return String.format(fmt, args);
   }

   static String reverse(String val) {
      return new StringBuilder(val).reverse().toString();
   }
}

These extension methods can be used as follows.

{str:format('%s %s!','Hello', 'world')} (1)
{str:reverse('hello')} (2)
1 The output is Hello world!
2 The output is olleh

3.5.3. Built-in Template Extensions

Quarkus provides a set of built-in extension methods.

3.5.3.1. Maps
  • keys or keySet: Returns a Set view of the keys contained in a map

    • {#for key in map.keySet}

  • values: Returns a Collection view of the values contained in a map

    • {#for value in map.values}

  • size: Returns the number of key-value mappings in a map

    • {map.size}

  • isEmpty: Returns true if a map contains no key-value mappings

    • {#if map.isEmpty}

  • get(key): Returns the value to which the specified key is mapped

    • {map.get('foo')}

A map value can be also accessed directly: {map.myKey}. Use the bracket notation for keys that are not legal identifiers: {map['my key']}.
3.5.3.2. Collections
  • get(index): Returns the element at the specified position in a list

    • {list.get(0)}

  • reversed: Returns a reversed iterator over a list

    • {#for r in recordsList.reversed}

  • take: Returns the first n elements from the given list; throws an IndexOutOfBoundsException if n is out of range

    • {#for r in recordsList.take(3)}

  • takeLast: Returns the last n elements from the given list; throws an IndexOutOfBoundsException if n is out of range

    • {#for r in recordsList.takeLast(3)}

A list element can be accessed directly: {list.10} or {list[10]}.
3.5.3.3. Numbers
  • mod: Modulo operation

    • {#if counter.mod(5) == 0}

3.5.4. Strings

  • fmt or format: format the string instance via java.lang.String.format()

    • {myStr.fmt("arg1","arg2")}

    • {myStr.format(locale,arg1)}

  • str:fmt or str:format: format the supplied string value via java.lang.String.format()

    • {str:format("Hello %s!",name)}

    • {str:fmt(locale,'%tA',now)}

3.5.4.1. Config
  • config:<name> or config:[<name>]: Returns the config value for the given property name

    • {config:foo} or {config:['property.with.dot.in.name']}

  • config:property(name): Returns the config value for the given property name; the name can be obtained dynamically by an expression

    • {config:property('quarkus.foo')}

    • {config:property(foo.getPropertyName())}

  • config:boolean(name): Returns the config value for the given property name as a boolean; the name can be obtained dynamically by an expression

    • {config:boolean('quarkus.foo.boolean') ?: 'Not Found'}

    • {config:boolean(foo.getPropertyName()) ?: 'property is false'}

  • config:integer(name): Returns the config value for the given property name as an integer; the name can be obtained dynamically by an expression

    • {config:integer('quarkus.foo')}

    • {config:integer(foo.getPropertyName())}

3.5.4.2. Time
  • format(pattern): Formats temporal objects from the java.time package

    • {dateTime.format('d MMM uuuu')}

  • format(pattern,locale): Formats temporal objects from the java.time package

    • {dateTime.format('d MMM uuuu',myLocale)}

  • format(pattern,locale,timeZone): Formats temporal objects from the java.time package

    • {dateTime.format('d MMM uuuu',myLocale,myTimeZoneId)}

  • time:format(dateTime,pattern): Formats temporal objects from the java.time package, java.util.Date, java.util.Calendar and java.lang.Number

    • {time:format(myDate,'d MMM uuuu')}

  • time:format(dateTime,pattern,locale): Formats temporal objects from the java.time package, java.util.Date, java.util.Calendar and java.lang.Number

    • {time:format(myDate,'d MMM uuuu', myLocale)}

  • time:format(dateTime,pattern,locale,timeZone): Formats temporal objects from the java.time package, java.util.Date, java.util.Calendar and java.lang.Number

    • {time:format(myDate,'d MMM uuuu',myTimeZoneId)}

3.6. @TemplateData

A value resolver is automatically generated for a type annotated with @TemplateData. This allows Quarkus to avoid using reflection to access the data at runtime.

Non-public members, constructors, static initializers, static, synthetic and void methods are always ignored.
package org.acme;

@TemplateData
class Item {

    public final BigDecimal price;

    public Item(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getDiscountedPrice() {
        return price.multiply(new BigDecimal("0.9"));
    }
}

Any instance of Item can be used directly in the template:

{#each items} (1)
  {it.price} / {it.discountedPrice}
{/each}
1 items is resolved to a list of org.acme.Item instances.

Furthermore, @TemplateData.properties() and @TemplateData.ignore() can be used to fine-tune the generated resolver. Finally, it is also possible to specify the "target" of the annotation - this could be useful for third-party classes not controlled by the application:

@TemplateData(target = BigDecimal.class)
@TemplateData
class Item {

    public final BigDecimal price;

    public Item(BigDecimal price) {
        this.price = price;
    }
}
{#each items} (1)
  {it.price.setScale(2, rounding)} (1)
{/each}
1 The generated value resolver knows how to invoke the BigDecimal.setScale() method.

3.7. Native Executables

In the JVM mode a reflection-based value resolver may be used to access properties and call methods of the model classes. But this does not work for a native executable out of the box. As a result, you may encounter template exceptions like Property "name" not found on the base object "org.acme.Foo" in expression {foo.name} in template hello.html even if the Foo class declares a relevant getter method.

There are several ways to solve this problem:

  • Make use of type-safe templates or type-safe expressions

    • In this case, an optimized value resolver is generated automatically and used at runtime

    • This is the preferred solution

  • Annotate the model class with @TemplateData - a specialized value resolver is generated and used at runtime

  • Annotate the model class with @io.quarkus.runtime.annotations.RegisterForReflection to make the reflection-based value resolver work

3.8. RESTEasy Integration

If you want to use Qute in your JAX-RS application, then depending on which JAX-RS stack you are using, you’ll need to register the proper extension first. If you are using the traditional quakus-resteasy extension, then in your pom.xml file, add:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-qute</artifactId>
</dependency>

If instead you are using RESTEasy Reactive via the quarkus-resteasy-reactive extension, then in your pom.xml file, add:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive-qute</artifactId>
</dependency>

Both of these extensions register a special ContainerResponseFilter implementation which enables resource methods to return a TemplateInstance, thus freeing users of having to take care of all necessary internal steps.

The end result is that a using Qute within a JAX-RS resource may look as simple as:

HelloResource.java
package org.acme.quarkus.sample;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;

@Path("hello")
public class HelloResource {

    @Inject
    Template hello; (1)

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public TemplateInstance get(@QueryParam("name") String name) {
        return hello.data("name", name); (2) (3)
    }
}
1 If there is no @Location qualifier provided, the field name is used to locate the template. In this particular case, we’re injecting a template with path templates/hello.txt.
2 Template.data() returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key name. The data map is accessible during rendering.
3 Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation.
Users are encouraged to use Type-safe templates that help to organize the templates for a specific JAX-RS resource and enable type-safe expressions automatically.

The content negotiation is performed automatically. The resulting output depends on the Accept header received from the client.

@Path("/detail")
class DetailResource {

    @Inject
    Template item; (1)

    @GET
    @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
    public TemplateInstance item() {
        return item.data("myItem", new Item("Alpha", 1000)); (2)
    }
}
1 Inject a variant template with base path derived from the injected field - src/main/resources/templates/item.
2 For text/plain the src/main/resources/templates/item.txt template is used. For text/html the META-INF/resources/templates/item.html template is used.

The RestTemplate util class can be used to obtain a template instance from a body of a JAX-RS resource method:

RestTemplate Example
@Path("/detail")
class DetailResource {

    @GET
    @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
    public TemplateInstance item() {
        return RestTemplate.data("myItem", new Item("Alpha", 1000)); (1)
    }
}
1 The name of the template is derived from the resource class and method name; DetailResource/item in this particular case.
Unlike with @Inject the templates obtained via RestTemplate are not validated, i.e. the build does not fail if a template does not exist.

3.9. Development Mode

In the development mode, all files located in src/main/resources/templates are watched for changes and modifications are immediately visible.

3.10. Type-safe Message Bundles

3.10.1. Basic Concepts

The basic idea is that every message is potentially a very simple template. In order to prevent type errors a message is defined as an annotated method of a message bundle interface. Quarkus generates the message bundle implementation at build time. Subsequently, the bundles can be used at runtime:

  1. Directly in your code via io.quarkus.qute.i18n.MessageBundles#get(); e.g. MessageBundles.get(AppMessages.class).hello_name("Lucie")

  2. Injected in your beans via @Inject; e.g. @Inject AppMessages

  3. Referenced in the templates via the message bundle namespace:

     {msg:hello_name('Lucie')} (1) (2) (3)
     {msg:message(myKey,'Lu')} (4)
    1 msg is the default namespace.
    2 hello_name is the message key.
    3 Lucie is the parameter of the message bundle interface method.
    4 It is also possible to obtain a localized message for a key resolved at runtime using a reserved key message. The validation is skipped in this case though.
Message Bundle Interface Example
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;

@MessageBundle (1)
public interface AppMessages {

    @Message("Hello {name}!") (2)
    String hello_name(String name); (3)
}
1 Denotes a message bundle interface. The bundle name is defaulted to msg and is used as a namespace in templates expressions, e.g. {msg:hello_name}.
2 Each method must be annotated with @Message. The value is a qute template.
3 The method parameters can be used in the template.

3.10.2. Bundle Name and Message Keys

Keys are used directly in templates. The bundle name is used as a namespace in template expressions. The @MessageBundle can be used to define the default strategy used to generate message keys from method names. However, the @Message can override this strategy and even define a custom key. By default, the annotated element’s name is used as-is. Other possibilities are:

  1. De-camel-cased and hyphenated; e.g. helloName()hello-name

  2. De-camel-cased and parts separated by underscores; e.g. helloName()hello_name.

3.10.3. Validation

  • All message bundle templates are validated:

    • All expressions without a namespace must map to a parameter; e.g. Hello {foo} → the method must have a param of name foo

    • All expressions are validated against the types of the parameters; e.g. Hello {foo.bar} where the parameter foo is of type org.acme.Fooorg.acme.Foo must have a property of name bar

      A warning message is logged for each unused parameter.
  • Expressions that reference a message bundle method, such as {msg:hello(item.name)}, are validated too.

3.10.4. Localization

The default locale of the Java Virtual Machine used to build the application is used for the @MessageBundle interface by default. However, the io.quarkus.qute.i18n.MessageBundle#locale() can be used to specify a custom locale. Additionally, there are two ways to define a localized bundle:

  1. Create an interface that extends the default interface that is annotated with @Localized

  2. Create an UTF-8 encoded file located in src/main/resources/messages; e.g. msg_de.properties.

A localized interface is the preferred solution mainly due to the possibility of easy refactoring.
Localized Interface Example
import io.quarkus.qute.i18n.Localized;
import io.quarkus.qute.i18n.Message;

@Localized("de") (1)
public interface GermanAppMessages extends AppMessages {

    @Override
    @Message("Hallo {name}!") (2)
    String hello_name(String name);
}
1 The value is the locale tag string (IETF).
2 The value is the localized template.

Message bundle files must be encoded in UTF-8. The file name consists of the relevant bundle name (e.g. msg) and underscore followed by the locate tag (IETF). The file format is very simple: each line represents either a key/value pair with the equals sign used as a separator or a comment (line starts with #). Blank lines are ignored. Keys are mapped to method names from the corresponding message bundle interface. Values represent the templates normally defined by io.quarkus.qute.i18n.Message#value(). A value may be spread out across several adjacent normal lines. In such case, the line terminator must be escaped with a backslash character \. The behavior is very similar to the behavior of the java.util.Properties.load(Reader) method.

Localized File Example - msg_de.properties
# This comment is ignored
hello_name=Hallo {name}! (1) (2)
1 Each line in a localized file represents a key/value pair. The key must correspond to a method declared on the message bundle interface. The value is the message template.
2 Keys and values are separated by the equals sign.
We use the .properties suffix in our example because most IDEs and text editors support syntax highlighting of .properties files. But in fact, the suffix could be anything - it is just ignored.
An example properties file is generated into the target directory for each message bundle interface automatically. For example, by default if no name is specified for @MessageBundle the file target/qute-i18n-examples/msg.properties is generated when the application is build via mvn clean package. You can use this file as a base for a specific locale. Just rename the file - e.g. msg_fr.properties, change the message templates and move it in the src/main/resources/messages directory.
Value Spread Out Across Several Adjacent Lines
hello=Hello \
   {name} and \
   good morning!

Note that the line terminator is escaped with a backslash character \ and white space at the start of the following line is ignored. I.e. {msg:hello('Edgar')} would be rendered as Hello Edgar and good morning!.

Once we have the localized bundles defined we need a way to select the correct bundle. If you use a message bundle expression in a template you’ll have to specify the locale attribute of a template instance.

locale Attribute Example
@Singleton
public class MyBean {

    @Inject
    Template hello;

    String render() {
       return hello.instance().setAttribute("locale", Locale.forLanguageTag("cs")).render(); (1)
    }
}
1 You can set a Locale instance or a locale tag string (IETF).
When using quarkus-resteasy-qute the locale attribute is derived from the the Accept-Language header if not set by a user.

The @Localized qualifier can be used to inject a localized message bundle interface.

Injected Localized Message Bundle Example
@Singleton
public class MyBean {

    @Localized("cs") (1)
    AppMessages msg;

    String render() {
       return msg.hello_name("Jachym");
    }
}
1 The annotation value is a locale tag string (IETF).

3.11. Configuration Reference

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

The list of suffixes used when attempting to locate a template file.

By default, engine.getTemplate("foo") would result in several lookups: foo, foo.html, foo.txt, etc.

list of string

qute.html,qute.txt,html,txt

The list of exclude rules used to intentionally ignore some parts of an expression when performing type-safe validation. An element value must have at least two parts separated by dot. The last part is used to match the property/method name. The prepended parts are used to match the class name. The value * can be used to match any name. Examples: - org.acme.Foo.name - exclude the property/method name on the org.acme.Foo class - org.acme.Foo.* - exclude any property/method on the org.acme.Foo class - *.age - exlude the property/method age on any class

list of string

The strategy used when a standalone expression evaluates to a "not found" value at runtime and the io.quarkus.qute.strict-rendering config property is set to false This strategy is never used when evaluating section parameters, e.g. {#if foo.name}. In such case, it’s the responsibility of the section to handle this situation appropriately. By default, the NOT_FOUND constant is written to the output. However, in the development mode the PropertyNotFoundStrategy#THROW_EXCEPTION is used by default, i.e. when the strategy is not specified.

default, noop, throw-exception, output-original

Specify whether the parser should remove standalone lines from the output. A standalone line is a line that contains at least one section tag, parameter declaration, or comment but no expression and no non-whitespace character.

boolean

true

If set to true then any expression that is evaluated to a Results.NotFound value will always result in a TemplateException and the rendering is aborted. Note that the quarkus.qute.property-not-found-strategy config property is completely ignored if strict rendering is enabled.

boolean

true

The additional map of suffixes to content types. This map is used when working with template variants. By default, the java.net.URLConnection#getFileNameMap() is used to determine the content type of a template file.

Map<String,String>

4. Qute Used as a Standalone Library

Qute is primarily designed as a Quarkus extension. However. it is possible to use it as a "standalone" library. In this case, some features are not available and some additional configuration is needed.

Engine

First of all, no managed Engine instance is available out of the box. You’ll need to configure a new instance via Engine.builder().

Templates
  • By default, no template locators are registered, i.e. Engine.getTemplate(String) will not work.

  • You can register a custom template locator or parse a template manually and put the reulst in the cache via Engine.putTemplate(String, Template).

Value resolvers
  • No ValueResolvers are generated automatically.

  • It’s recommended to register a ReflectionValueResolver instance via Engine.addValueResolver(new ReflectionValueResolver()) so that Qute can access object properties and call public methods.

    Keep in mind that reflection may not work correctly in some restricted environments or may require additional configuration, e.g. registration in case of a GraalVM native image.
  • A custom value resolver can be easily built via ValueResolver.builder()

Type-safety
Injection

It is not possible to inject a Template instance and vice versa - a template cannot inject a @Named CDI bean via the inject: namespace.