Qute Templating
|
This section documents Quarkus 3.39.x (current) with 3.33.x LTS as the production recommendation, on the Java 17+ baseline, as published at the official Quarkus guides — which are the reference these pages are written and verified against. Quarkus 4 raises the baseline to Java 21 (first release candidate planned for end of September 2026, see the Quarkus blog); readers upgrading past 3.39.x/3.33.x LTS should expect that change. This content was generated with the assistance of AI and should be verified against those official guides before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Qute is Quarkus’s own template engine, built from the ground up for the same build-time-augmentation model as the rest of the framework: as much of a template’s structure as possible — which variables it uses, which methods it calls on them, whether an included template even exists — is checked at build time rather than discovered by rendering the template and hoping nothing breaks in production. This page covers Qute’s syntax, its signature type-safe-template feature, where templates live and how they’re rendered, and the two adjacent Quarkiverse projects (Quinoa and Renarde) that build on top of the same web layer.
Syntax
A Qute template is ordinary text with {…} expressions and {#tag}…{/tag} sections spliced in:
<h1>Hello {name}!</h1>
{#if items.isEmpty}
<p>Your cart is empty.</p>
{#else}
<ul>
{#for item in items}
<li>{item.name} -- {item.price}</li>
{/for}
</ul>
{/if}
{#let total=items.size}
<p>{total} item(s) in your cart.</p>
{/let}
-
{expression}evaluates a property path, a method call, or an operator expression and renders the result, HTML-escaped by default (see HTML escaping and safety below). -
{#if}/{#else}/{/if}and{#for item in collection}/{/for}are the core control-flow sections; Qute also ships{#with},{#switch}/{#case}, and{#include}for composing templates from fragments. -
{#let name=expression}…{/let}binds a local variable for the rest of the section, useful for avoiding repeated method calls in a hot loop. -
Standard operators (
==,!=,&&,||,>,<,+,-, ternary?:) work inside{…}expressions the same way they would in Java, plus a null-safe traversal operator (item.address??.city) that short-circuits tonull/not-rendered instead of throwing when an intermediate property is absent.
Value resolvers and template extension methods
Qute resolves item.name by first trying a public getter/field the normal Java way, then falling back to a chain
of value resolvers — pluggable strategies for reading a property off a type Qute doesn’t otherwise know how to
introspect (a Map, a record, a type from a library whose source isn’t annotated for Qute). A template extension
method adds a virtual "property" or method to a type you don’t own, without subclassing or wrapping it:
package com.example.web;
import io.quarkus.qute.TemplateExtension;
import java.math.BigDecimal;
@TemplateExtension
public class MoneyExtensions {
static String formatted(BigDecimal amount) {
return "$" + amount.setScale(2, java.math.RoundingMode.HALF_UP);
}
}
Once registered, {item.price.formatted} (or {item.price.formatted()}) is available inside any template, exactly
as if BigDecimal had a formatted property of its own.
Type-safe templates: @CheckedTemplate
Referencing a template by a bare file name and building its data model as a loose Map<String, Object> defers
every mistake — a missing variable, a typo’d key, a template that no longer exists — to the first request that
actually renders it. @CheckedTemplate turns each templated method into a compile-time-checked, IDE-navigable
call by generating a static factory whose parameters are the template’s declared variables:
package com.example.web;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import java.math.BigDecimal;
import java.util.List;
public class OrderPages {
@CheckedTemplate
public static class Templates {
// one static method per template file in this class's templates/OrderPages/ directory;
// the parameter names must match the template's variable names exactly
public static native TemplateInstance summary(String customerName, List<LineItem> items, BigDecimal total);
}
public record LineItem(String name, BigDecimal price) {
}
}
<!-- src/main/resources/templates/OrderPages/summary.html -->
<h1>Order summary for {customerName}</h1>
<ul>
{#for item in items}
<li>{item.name} -- {item.price}</li>
{/for}
</ul>
<p>Total: {total}</p>
The build fails — not the first request — if summary.html is missing, if a parameter name in the Java method
doesn’t match a variable the template actually uses, or if a property access inside the template can be proven
not to exist on the declared parameter’s type. This is the single biggest reason Qute templates are considered
noticeably safer to refactor than a typical untyped template engine’s.
Template records
A record can stand in directly as a template’s data holder instead of listing every field as a separate
@CheckedTemplate parameter, keeping the method signature short when a template’s variables naturally group
together:
package com.example.web;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
public class GreetingPages {
public record Greeting(String name, boolean vip) {
}
@CheckedTemplate
public static class Templates {
public static native TemplateInstance greeting(Greeting greeting);
}
}
Injecting and rendering
A resource method returns a TemplateInstance (or a reactive Uni<TemplateInstance>, covered below) directly — Quarkus REST recognizes it and renders it as the response body, content-negotiated the same way any other
resource return type would be:
package com.example.web;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.quarkus.qute.TemplateInstance;
@Path("/orders")
public class OrderResource {
@GET
public TemplateInstance summary() {
return OrderPages.Templates.summary("Ada", java.util.List.of(), java.math.BigDecimal.ZERO);
}
}
An untyped Template can also be @Inject`ed by file name and given data with `.data(key, value) calls, which is
occasionally convenient for a small, one-off template but forgoes `@CheckedTemplate’s build-time checking.
Template locations, i18n, escaping, and reactive rendering
Template locations and content negotiation
Templates live under src/main/resources/templates/ by convention, in a subdirectory matching the declaring
class’s simple name for @CheckedTemplate methods (as shown above). A single logical template can also ship
several variants — summary.html, summary.txt — and Qute picks the one matching the request’s Accept
header automatically, the same content-negotiation idea Quarkus REST itself uses for JSON vs. XML.
i18n message bundles
@MessageBundle-annotated interfaces work the same way @CheckedTemplate does, but for localized text rather
than whole templates: one interface method per message key, resolved against locale-specific
msg_<locale>.properties files, and directly callable from a template as {msg:key(args)} or from Java code
through the generated implementation.
HTML escaping and safety
Every {expression} inside an .html-suffixed template is HTML-escaped by default — the same safe-by-default
posture as most modern template engines — which is what makes {item.name} safe to render even when name
originated from user input. The {expression.raw} built-in resolver (or the {#raw}…{/raw} section for a whole
block) opts out of escaping explicitly, and should only ever be reached for when the content is already known-safe
markup generated by the application itself, never for anything traceable back to user input.
Rendering reactively
Template.data(…).createUni() (or the generated @CheckedTemplate method’s own Uni-returning overload) yields
a Uni<String>/Uni<TemplateInstance> instead of blocking the calling thread while the template renders — relevant once a template’s data model itself comes from a reactive source (a reactive datasource query, a
downstream REST-client call) and the whole request path is meant to stay non-blocking end to end; see
Reactive Programming and Mutiny for Uni itself.
Mail templates
The Mailer extension (covered on the scheduling-and-mail page in this section) accepts a Qute template directly as
an email body, with the same @CheckedTemplate type-safety and HTML-escaping rules applying to the rendered
message — there is no separate, less-safe templating path for outgoing mail.
Quinoa and Renarde
Two Quarkiverse projects build on the same Quarkus web layer this page and rest-services.adoc describe, for two
different shapes of application:
Quinoa bundles a modern JavaScript single-page-application front end (React, Vue, Angular, or any Node-based
build) directly into a Quarkus application’s build and dev-mode loop — the SPA’s own dev server proxies through
quarkus dev during development, and its production build is packaged straight into the Quarkus artifact, so a
full-stack project ships as one deployable unit with one quarkus dev inner loop instead of two separate
front-end/back-end toolchains running side by side.
Renarde is a server-side web framework built on top of Qute and Quarkus REST for applications that render HTML on the server rather than serving a JSON API to a separate SPA — convention-based controllers, form handling, and flash messages in the Ruby-on-Rails/Django tradition, aimed at the traditional server-rendered-web-app shape Quinoa’s SPA model deliberately isn’t.