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 to null/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.